Обмеження автоматичного макетування на CALayer IOS


78

Привіт, я розробляю додаток для iPhone, в якому я намагався встановити одну бічну межу для редагування тексту. Я зробив це наступним чином:

 int borderWidth = 1;
CALayer *leftBorder = [CALayer layer];

leftBorder.borderColor = [UIColor whiteColor].CGColor;
leftBorder.borderWidth = borderWidth;

leftBorder.frame = CGRectMake(0, textField.frame.size.height - borderWidth, textField
                              .frame.size.width, borderWidth);
[textField.layer addSublayer:leftBorder];

Я поклав деякі обмеження на мій текст редагування в IB, щоб, коли я обертаю пристрій, він регулював ширину текстового поля відповідно до цього. Моя проблема полягає в тому, що регулює ширину редагування тексту, а не ширину CALayer, яку я встановив для мого редагування тексту. Тому я думаю, що мені доведеться встановити деякі обмеження і для мого елемента CALayer. Але я не знаю, як це зробити. Хто-небудь знає про це? Потрібна допомога. Дякую.


1
неможливо. Обмеження працюють лише з UIView
Макс Маклауд

Відповіді:


122

весь бізнес автоматичного розміру залежить від перегляду. шари не автоматично змінюються.

що вам потрібно зробити - у коді - це змінити розмір шару самостійно

напр

у viewController, який ви зробите

- (void) viewDidLayoutSubviews {
  [super viewDidLayoutSubviews]; //if you want superclass's behaviour... 
  // resize your layers based on the view's new frame
  self.editViewBorderLayer.frame = self.editView.bounds;
}

або в користувацькому UIView, яким ви можете скористатися

- (void)layoutSubviews {
  [super layoutSubviews]; //if you want superclass's behaviour...  (and lay outing of children)
  // resize your layers based on the view's new frame
  layer.frame = self.bounds;
}

1
Якщо ви використовуєте layoutSubviews, вам слід зателефонувати йому на super
rmp251

@ rmp251 ні документи, ні файли заголовків не говорять про це, і це здається дивним. я маю на увазі, що я б зберіг лише поведінку за замовчуванням - автоматичне розміщення (на ios51 +) .. можливо, це те, що хочеться, але це не завжди правильно ... так що ніякого AFAIK
Daij-Djan

21
Це рішення не працює, якщо подання, що володіє шаром, має розмір за допомогою обмежень авторозкладки. У цьому випадку властивості меж та фреймів не відображають фактичні межі / фрейм, як це визначається обмеженнями. Будь-які пропозиції?
Альфі Ганссен,

3
@AlfieHanssen вони відображають фактичні рамки та межі. LayoutSubviews це робить. Ви також можете запускати після ViewDidAppear. Також дзвонить layoutIfNeeded, InvalidateIntrinsic тощо ...
Нік Тернер

2
Це також не працює, коли ви анімуєте обмеження авторозкладки за допомогою layoutIfNeed у поданні, яке має кілька підшарів, які повинні відображати рамку подання ..
dvdblk

10

В Swift 5, ви можете спробувати наступне рішення:

Використовуйте KVO для шляху "YourView.bounds", як зазначено нижче.

self.addObserver(self, forKeyPath: "YourView.bounds", options: .new, context: nil)

Потім обробіть це, як показано нижче.

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "YourView.bounds" {
            YourLayer.frame = YourView.bounds
            return
        }
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }

Більше інформації про це тут


1
Це спрацювало чудово, і я думаю, що це найкращий підхід для "авторозкладки", на відміну від того, щоб робити це у * Layoutsubviews
Junior

URL-адреса у відповіді не працює.
ВСЕФУЛ

7

Відповідно до цієї відповіді, layoutSubviewsне телефонують у всіх необхідних випадках. Я знайшов цей метод делегування більш ефективним:

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if(self != nil) {
        [self.layer addSublayer:self.mySublayer];
    }
    return self;
}

- (void)layoutSublayersOfLayer:(CALayer*)layer
{
    self.mySublayer.frame = self.bounds;
}

5

Я реалізував метод layoutSubviews мого власного подання; всередині цього методу я просто оновлюю фрейми кожного підшару відповідно до поточних меж шару мого підподілу.

-(void)layoutSubviews{
      sublayer1.frame = self.layer.bounds;
}

Найкраще рішення!
Mc.Lover

8
дзвінок супер щонайменше
Daij-Djan

5

Моє рішення, коли я створюю з пунктиром

class DashedBorderView: UIView {

   let dashedBorder = CAShapeLayer()

   override init(frame: CGRect) {
       super.init(frame: frame)
       commonInit()
   }

   required init?(coder aDecoder: NSCoder) {
       super.init(coder: aDecoder)
       commonInit()
   }

   private func commonInit() {
       //custom initialization
       dashedBorder.strokeColor = UIColor.red.cgColor
       dashedBorder.lineDashPattern = [2, 2]
       dashedBorder.frame = self.bounds
       dashedBorder.fillColor = nil
       dashedBorder.cornerRadius = 2
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       self.layer.addSublayer(dashedBorder)
   }

   override func layoutSublayers(of layer: CALayer) {
       super.layoutSublayers(of: layer)
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       dashedBorder.frame = self.bounds
   }
}

Працював як оберіг.
user246392

1

Рішення Swift 3.x KVO (оновлена ​​відповідь @ arango_86)

Додати спостерігача

self.addObserver(
    self, 
    forKeyPath: "<YOUR_VIEW>.bounds", 
    options: .new, 
    context: nil
)

Дотримуйтесь цінностей

override open func observeValue(
    forKeyPath keyPath: String?, 
    of object: Any?, 
    change: [NSKeyValueChangeKey : Any]?, 
    context: UnsafeMutableRawPointer?
) {
    if (keyPath == "<YOUR_VIEW.bounds") {
      updateDashedShapeLayerFrame()
      return
    }

    super.observeValue(
        forKeyPath: keyPath, 
        of: object, 
        change: change, 
        context: context
    )
}

1

Коли мені доводиться застосовувати KVO, я вважаю за краще робити це за допомогою RxSwift (Тільки якщо я використовую його для більшої кількості матеріалів, не додайте цю бібліотеку лише для цього.)

Ви можете застосувати KVO і до цієї бібліотеки, або в, viewDidLayoutSubviewsале я мав кращі результати з цим.

  view.rx.observe(CGRect.self, #keyPath(UIView.bounds))
        .subscribe(onNext: { [weak self] in
            guard let bounds = $0 else { return }
            self?.YourLayer.frame = bounds
        })
        .disposed(by: disposeBag)

0

Приховуючи відповідь arango_86 - якщо ви застосовуєте виправлення KVO у своєму власному підкласі UIView, більш «швидкий» спосіб зробити це - перевизначити boundsта використовувати didSetна ньому, наприклад:

override var bounds: CGRect {
    didSet {
        layer.frame = bounds
    }
}

-1

У мене була подібна проблема - необхідність встановити фрейм "CALayer" при використанні автоматичного макетування з поданнями (у коді, а не IB ).

У моєму випадку я мав дещо заплутану ієрархію, маючи контролер перегляду всередині контролера перегляду. Я опинився на цьому SO-питанні і розглянув підхід використання viewDidLayoutSubviews. Це не спрацювало. На випадок, якщо ваша ситуація схожа на мою, ось що я знайшов ...

Огляд

Я хотів би встановити рамки для CAGradientLayerоператора А , UIViewщо я позиціонував як підвид в межах з UIViewControllerдопомогою обмежень автоматичної компонування.

Викличте підзапис gradientViewта контролер перегляду child_viewController.

child_viewControllerбув контролером перегляду, який я встановив як свого роду повторно використовуваний компонент. Отже, viewкомпонент child_viewControllerкомпонувався у батьківський контролер подання - зателефонуйте parent_viewController.

Коли viewDidLayoutSubviewsз child_viewControllerназивали, то frameз gradientViewще не був встановлений.

(На цьому етапі я рекомендую посипати деякі NSLogтвердження навколо, щоб відчути послідовність створення поглядів у вашій ієрархії тощо).

Тож я перейшов на спробу використання viewDidAppear. Однак через вкладений характер child_viewControllerмене знайшли viewDidAppearне викликали.

(Див. Це запитання SO: viewWillAppear, viewDidAppear не викликається, не запускається ).

Моє поточне рішення

Я додав viewDidAppearу parent_viewControllerі звідти я дзвоню viewDidAppearв child_viewController.

Для початкового завантаження мені потрібно, viewDidAppearоскільки це не відбувається, поки це не буде викликано child_viewController, і всі підпрогляди мають встановлені кадри. Потім я можу встановити рамку для CAGradientLayer...

Я сказав, що це моє поточне рішення, тому що я не особливо ним задоволений.

Після початкового встановлення кадру CAGradientLayer- цей кадр може стати недійсним у разі зміни макета - наприклад, обертаючи пристрій.

Щоб впоратися з цим, я використовую viewDidLayoutSubviewsв child_viewController- щоб зберегти рамку CAGradientLayerвсередині gradientView, правильно.

Це працює, але не відчуває себе добре. (Чи є кращий спосіб?)


1
Як правило, ви не повинні викликати способи вигляду виду / життєвого циклу вручну. У цьому конкретному випадку Дайдж-Джан має правильну ідею.
Самі Самхурі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.