У нас есть странный случай, когда наше представление выглядит не так, как ожидалось, и мы проследили его до AutoLayout и некоторой последовательности событий, которые мы не можем понять, почему это изменило бы вещи.
Чтобы упростить Например, вот некоторый тестовый код, который вы можете запустить непосредственно на игровой площадке Xcode. (Мы используем Xcode 11.3.1, если это имеет значение.)
Сначала мы определяем оператор InlineConfigure
, например так ...
// 'InlineConfigure' operator
infix operator ~> : AssignmentPrecedence
@discardableResult
public func ~> <T>(item:T, _ configure:(T)->Void) -> T {
configure(item)
return item
}
Далее, чтобы проиллюстрировать, что это не так В случае, когда что-то освобождается, когда не ожидается, мы создаем подкласс UIView
специально для регистрации этого ...
class SomeView : UIView {
override init(frame: CGRect) {
super.init(frame:frame)
print("SomeView created")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("SomeView deinitialized")
}
}
С обоими из вышеперечисленных рассмотрим этот код. Он работает, как и ожидалось.
class TestViewController : UIViewController {
let margin:CGFloat = 24
override func loadView() {
view = UIView()
view.backgroundColor = .yellow
SomeView()~>{
$0.backgroundColor = .blue
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
NSLayoutConstraint.activate([
$0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: margin),
$0.topAnchor.constraint(equalTo: view.topAnchor, constant: margin),
$0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -margin),
$0.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -margin)
])
}
}
}
PlaygroundPage.current.liveView = TestViewController()
Если мы, однако, изменим строку, которая создает SomeView
, на кадр, имеющий ненулевую область (например, ширину и высоту), то этот же код больше не работает, и вы вообще не видите синий вид.
override func loadView() {
view = UIView()
view.backgroundColor = .yellow
SomeView(frame:CGRect(x: 0, y: 0, width: 20, height: 20))~>{
$0.backgroundColor = .blue
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
NSLayoutConstraint.activate([
$0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: margin),
$0.topAnchor.constraint(equalTo: view.topAnchor, constant: margin),
$0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -margin),
$0.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -margin)
])
}
}
Установка рамки на нулевую область, и она снова работает ...
SomeView(frame:CGRect(x: 0, y: 0, width: 0, height: 20))~>{
[...]
}
Но назад в ненулевой фрейм, если вы переместите код установки ограничения за пределы закрытия конфигурации, то он всегда работает независимо от того, есть фрейм с областью или нет!
override func loadView() {
view = UIView()
view.backgroundColor = .yellow
let someView = SomeView(frame:CGRect(x: 0, y: 0, width: 20, height: 20))~>{
$0.backgroundColor = .blue
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
NSLayoutConstraint.activate([
someView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: margin),
someView.topAnchor.constraint(equalTo: view.topAnchor, constant: margin),
someView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -margin),
someView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -margin)
])
}
Что нас смущает, так это предыдущий код, $0
внутри замыкания, является строгой ссылкой на вновь созданное представление (и родительский элемент также содержит ссылку на него, когда он был добавлен как подпредставление, поэтому он не выгружается), и эта точно такая же ссылка назначена на someView
снаружи, где он затем выполняет точно такой же код, который был в замыкании. Между закрытием и кодом снаружи ничего нет, кроме этого присваивания, но оно дает разные результаты! Но почему?
Может кто-нибудь объяснить, почему мы видим такое поведение? Чего нам не хватает?