程序约束未按预期工作

Posted

技术标签:

【中文标题】程序约束未按预期工作【英文标题】:Programmatic Constraints not working as expected 【发布时间】:2017-03-04 06:11:10 【问题描述】:

我花了几个小时试图找出阻止我的约束布局工作的原因。我有一个名为 ABSegment 的视图,它只包含一个 UILabel,它应该居中并具有与其父视图相同的高度和宽度。

我将包含此 UIView 子视图的整个类定义以显示我所做的。

import UIKit

class ABSegment: UIView 

   let titleLabel = UILabel()

   init(withTitle title: String) 

      titleLabel.text = title
      titleLabel.textAlignment = .center

      super.init(frame: CGRect.zero)

      translatesAutoresizingMaskIntoConstraints = false
      titleLabel.translatesAutoresizingMaskIntoConstraints = false

      addSubview(titleLabel)

      backgroundColor = UIColor.blue
   


   override func layoutSubviews() 
      super.layoutSubviews()


      logFrames()
   


   func logFrames() 
      print("self.frame is \(frame)")
      print("titleLabel.frame is \(titleLabel.frame)")
   

   required init?(coder aDecoder: NSCoder) 
      fatalError("init(coder:) has not been implemented")
   



   override func updateConstraints() 


      logFrames()

      titleLabel.removeConstraints(titleLabel.constraints)

      let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)

      let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)

      let width = NSLayoutConstraint(item: titleLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)

      let height = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0)

      NSLayoutConstraint.activate([centerX, centerY, width, height])

      super.updateConstraints()
   




很自然地怀疑我没有用 initWithFrame 初始化这个视图。相反,我将框架设置推迟到构建这些视图的代码的后面。所以构造这些的代码不会设置框架。框架可以在 Storyboard 中设置或像这样:

   override func viewDidLayoutSubviews() 
      super.viewDidLayoutSubviews()

      let frame = CGRect(x: view.bounds.origin.x + 150, y: view.bounds.origin.y + 200, width: 100, height: 100)
      segment.frame = frame

      segment.layoutSubviews()

   

我的理解是,由于我正在调用segment.layoutSubviews(),因此 ABSegment 视图应该进行更改以将先前激活的约束应用于最终帧。

有很多不同的设置和事情要按照正确的顺序正确处理,除了根本看不到标签出现之外,没有任何反馈。

【问题讨论】:

【参考方案1】:

我看到的代码的主要问题:

    每次调用 updateConstraints 时,您都在添加和删除约束。您只需在创建视图时设置一次约束。

    您将 translatesAutoresizingMaskIntoConstraints = false 设置在 ABSegment 本身上。不要这样做。这告诉自动布局,您将使用约束来指定ABSegment 的大小和位置,并且显然您为此目的使用了frame


这是重构后的代码:

class ABSegment: UIView 

    let titleLabel = UILabel()

    // Computed property to allow title to be changed
    var title: String 
        set 
            titleLabel.text = newValue
        
        get 
            return titleLabel.text ?? ""
        
    

    override init(frame: CGRect) 
        super.init(frame: frame)

        setupLabel(title: "")
    

    convenience init(title: String) 

        self.init(frame: CGRect.zero)

        self.title = title
    

    // This init is called if your view is
    // set up in the Storyboard
    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)

        setupLabel(title: "")
    

    func setupLabel(title: String) 

        titleLabel.text = title

        titleLabel.textAlignment = .center

        addSubview(titleLabel)

        backgroundColor = UIColor.blue

        titleLabel.translatesAutoresizingMaskIntoConstraints = false

        let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)

        let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)

        let width = NSLayoutConstraint(item: titleLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)

        let height = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0)

        NSLayoutConstraint.activate([centerX, centerY, width, height])
    

    override func layoutSubviews() 
        super.layoutSubviews()
        logFrames()
    

    func logFrames() 
        print("self.frame is \(frame)")
        print("titleLabel.frame is \(titleLabel.frame)")
    

    override func updateConstraints()             
        super.updateConstraints()            
        logFrames()
    



注意事项:

    我将标签的所有设置移到了一个名为setupLabel 的函数中。这允许它被两个初始化器调用。 我向ABSegment 添加了一个名为title 的计算属性,它允许您随时使用mysegment.title = "new title" 更改title。 我把init(withSegment:) 变成了一个方便的初始化。它调用标准init(frame:),然后设置title。使用withProperty 不是一种常见的做法,所以我改变了它。您将使用var segment = ABSegment(title: "some title") 创建一个ABSegment。 我让required init?(coder aDecoder: NSCoder) 调用setupLabel,以便ABSegment 可以与故事板中添加的视图一起使用。 layoutSubviewsupdateConstraints 的覆盖被保留以记录帧。这就是他们现在所做的一切。

【讨论】:

太棒了!实际上,将自动调整大小的蒙版设置为约束是罪魁祸首。我也很欣赏您提供的其他编码技巧,以及在构造标签时只需要设置一次约束的知识。我认为应该在每个“updateConstraints”重新计算约束的部分原因是我将它与layoutSubviews 进行了比较。对于 layoutSubviews,我觉得值得为每个布局通道完成计算帧。 层次结构中的根视图可能会发生变化,然后所有后代视图框架都需要在 layoutSubviews 时计算新的矩形。随着约束旋转,我不确定这是否也需要。因此,您只需要在该视图的初始时间定义/激活约束。所以约束不共享相同的语义。

以上是关于程序约束未按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

IOS 8 高度约束动画未按预期工作

UITableViewCell 未按预期显示约束

分支测试/实时链接未按预期工作

Laravel调度程序未按预期工作

couchdb/cloudant 更新处理程序未按预期工作

UICollectionViewLayout 未按预期工作