如果为目标视图指定了初始帧,为啥 AutoLayout 不遵守约束?
Posted
技术标签:
【中文标题】如果为目标视图指定了初始帧,为啥 AutoLayout 不遵守约束?【英文标题】:Why would AutoLayout not respect constraints if an initial frame is specified for the target view?如果为目标视图指定了初始帧,为什么 AutoLayout 不遵守约束? 【发布时间】:2020-03-15 01:25:09 【问题描述】:我们有一个奇怪的情况,我们的视图没有按预期显示,我们已经追踪到 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
,然后它执行与闭包中完全相同的代码。除了赋值之外,闭包和外部代码之间没有任何东西,但它给出了不同的结果!但为什么呢?
谁能解释为什么我们会看到这种行为?我们缺少什么?
【问题讨论】:
【参考方案1】:我会说这是因为游乐场是魔鬼的作品。我在一个真正的 ios 应用项目中运行了您的代码,即使使用
,它也能按预期工作SomeView(frame:CGRect(x: 0, y: 0, width: 20, height: 20))
基本上我不希望 Playground 正确地经历视图控制器的所有生命周期。
【讨论】:
好废话!这太令人失望了!但只是表明 Playgrounds 不应该用于测试视觉事物。不过,我将把它作为雷达/错误提交给他们,因为情况不应该如此。以上是关于如果为目标视图指定了初始帧,为啥 AutoLayout 不遵守约束?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 UICollectionView 支持方法仅在我的 ViewController 设置为初始视图控制器时调用?