【问题标题】: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 视图应该进行更改以将先前激活的约束应用于最终帧。

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

【问题讨论】:

    标签: swift uiview autolayout constraints


    【解决方案1】:

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

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

    2. 您将 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()
        }
    
    }
    

    注意事项:

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

    【讨论】:

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