layoutSubviews vs frame didSet для определения изменения кадра представления - PullRequest
0 голосов
/ 05 сентября 2018

Скажем, у меня есть подкласс UIView, который должен что-то обновлять, когда меняется его фрейм. Например. когда он на четном пикселе, он будет красным и синим, когда он на нечетном пикселе.

Я видел как минимум четыре способа сделать это:

  1. view.layoutSubviews():

    override func layoutSubviews() {
        super.layoutSubviews()
        backgroundColor = calculateColorFromFrame(frame)
    }
    
  2. view.frame didSet:

    override var frame: CGRect {
        didSet {
            backgroundColor = calculateColorFromFrame(frame)
        }
    }
    
  3. Добавить наблюдателя KVO в рамку представления.

  4. В UIViewController для представления внедрите viewDidLayoutSubviews().

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        view.updateBackgroundColorForNewFrame()
    }
    
  5. Любой другой способ, о котором вы знаете?

Какой из этих методов предпочтительнее, и один из них по своей природе лучше другого? Какова официальная рекомендация Apple?

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

Возможно, я неправильно понял ваши вопросы или неверно истолковал документацию Apple, но вот мои мысли о том, что использовать, чтобы увидеть изменения фрейма:

  1. Вы не должны вызывать view.layoutSubviews () напрямую. Метод LayoutSubviews может быть переопределен подклассом UIView «только в том случае, если поведение подпредставлений, основанное на автоматическом изменении размера и ограничениях, не обеспечивает требуемого поведения. Вы можете использовать свою реализацию для непосредственной установки прямоугольников кадра для ваших подпредставлений». - Копировать из документации Apple. Поэтому я бы не использовал этот метод, так как он применяется не к представлению, для которого вы хотите изменить цвета, а к подпредставлениям вашего представления.
  2. Я думаю, что это очень элегантный способ использования, поскольку, если вы переместите вид, для рамки будут установлены новые значения. Однако, когда кадр меняет размер, также будет вызван didSet, и движение кадра может не произойти. Если ваш calcColorFromFrame (frame) является дорогим методом, и представление может изменить размер, это может привести к некоторой потере производительности.
  3. Я думаю, что это довольно громоздкий способ достичь того же, что и в пункте два. Единственное, что вы теперь можете использовать другой класс, чтобы наблюдать изменения в фрейме представления и вызывать метод calcColorFromFrame (frame).
  4. viewDidLayoutSubviews () в соответствующем контроллере представления также является хорошим способом, но здесь этот метод вызывается, когда что-то в главном представлении этого контроллера представления изменилось, и представление должно снова выложить свои подвиды. Это также означает, что этот метод может быть вызван, когда ваше представление не было перемещено или изменено, а calcColorFromFrame (frame) не изменилось. Поэтому это может снова вызывать этот метод пару раз, часто.

Что меня интересует, так это то, как эффективно вы собираетесь рассчитывать нечетные и четные пиксели, поскольку положение и размер кадра основаны на точках, а точка может занимать более одного пикселя.

Надеюсь, это ответило на ваш вопрос.

0 голосов
/ 05 сентября 2018
  1. layoutSubviews: Вы можете рассчитывать на то, что этот вызов вызывается для представления, размер которого изменился, или если представление получило setNeedsLayout. Если позиция представления изменяется, но его размер не изменяется, система автоматически не вызывает layoutSubviews для этого представления.

  2. frame didSet: Это работает для вида, который выложен с использованием autoresizingMask. Это не работает для представления, размеченного с использованием нормальных ограничений. Вместо этого автоматическое расположение устанавливает представления center и bounds. Однако это детали реализации, которые могут быть изменены в разных версиях UIKit. Я тестировал на iOS 12.0 Simulator, который поставляется с Xcode 10 beta 6.

  3. KVO на frame: Это не сработает при некоторых обстоятельствах по той же причине, что и # 2. Кроме того, frame не задокументировано как совместимое с KVO, поэтому UIKit разрешено изменять frame без уведомления наблюдателей.

  4. viewDidLayoutSubviews: Это может работать только в том случае, если представление является свойством view контроллера представления, и только в том случае, если view получает сообщение layoutSubviews (см. # 1). Также важно понимать, что когда это вызывается, view контроллера представления и его прямые подпредставления были выложены, но более глубокие подпредставления не были выложены еще. (См. этот ответ для более полного объяснения.)

Обратите внимание также, что frame представления вычисляется из некоторых свойств его layer: position, bounds.size, anchorPoint и transform слоя. Если что-то устанавливает эти свойства слоя напрямую, ни один из перечисленных выше методов не будет работать.

Из-за всего этого нет особого отличного способа получать уведомления при изменении позиции представления. Технически правильный путь, вероятно, заключается в использовании CAAction. Когда уровень обнаруживает, что его position изменяется, он запрашивает у своего делегата (который представляет собой само представление для слоя представления) действие, которое нужно запустить. Он запрашивает, отправив сообщение actionForLayer:forKey:. Таким образом, вы можете переопределить action(forLayer:forKey:) в своем подклассе представления, чтобы получать надежные уведомления. Однако слой запрашивает действие перед тем, как изменит свое position (и, следовательно, frame). Он запускает действие после изменения его position. Таким образом, вы должны пройти небольшой танец, как это:

override func action(for layer: CALayer, forKey event: String) -> CAAction? {
    class MyAction: CAAction {
        init(view: MyView, nextAction: CAAction?) {
            self.view = view
            self.nextAction = nextAction
        }
        let view: MyView
        let nextAction: CAAction?
        func run(forKey event: String, object: Any, arguments: [AnyHashable : Any]?) {


            // THIS IS WHERE YOU LOOK AT THE NEW FRAME AND ACT ACCORDINGLY.


            print("This is the action. Frame now: \(view.frame)")
            nextAction?.run(forKey: event, object: object, arguments: arguments)
        }
    }

    let action = super.action(for: layer, forKey: event)
    if event == "position" {
        return MyAction(view: self, nextAction: action)
    } else {
        return action
    }
}

Реализация UIView actionForLayer:forKey: обычно возвращает nil. Но внутри анимационного блока (в том числе, когда интерфейс вращается между портретным и альбомным), он возвращает (для некоторых клавиш) действие, которое устанавливает CAAnimation на слой. Поэтому важно запустить действие, возвращаемое super, если оно есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...