如果为目标视图指定了初始帧,为啥 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 不遵守约束?的主要内容,如果未能解决你的问题,请参考以下文章

为啥视图的阴影被创建为内部

为啥当附加到 nil 图像时,帧修饰符仍然会影响其他视图?

为啥我的 UICollectionView 支持方法仅在我的 ViewController 设置为初始视图控制器时调用?

关键帧动画问题

为啥我的淘汰视图模型使用不同的值初始化?

当我将此数据帧转换为字典时,为啥索引设置不正确?