layoutSubviews
: Вы можете рассчитывать на то, что этот вызов вызывается для представления, размер которого изменился, или если представление получило setNeedsLayout
. Если позиция представления изменяется, но его размер не изменяется, система автоматически не вызывает layoutSubviews
для этого представления.
frame didSet
: Это работает для вида, который выложен с использованием autoresizingMask
. Это не работает для представления, размеченного с использованием нормальных ограничений. Вместо этого автоматическое расположение устанавливает представления center
и bounds
. Однако это детали реализации, которые могут быть изменены в разных версиях UIKit. Я тестировал на iOS 12.0 Simulator, который поставляется с Xcode 10 beta 6.
KVO на frame
: Это не сработает при некоторых обстоятельствах по той же причине, что и # 2. Кроме того, frame
не задокументировано как совместимое с KVO, поэтому UIKit разрешено изменять frame
без уведомления наблюдателей.
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
, если оно есть.