程序化约束不能按预期工作

时间:2017-03-04 06:11:11

标签: swift uiview autolayout constraints

我花了好几个小时试图找出妨碍我的约束布局工作的因素。我有一个名为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 View应该有一个更改,以便在最后一帧中应用先前激活的约束。

有许多不同的设置和事情以正确的顺序排列,没有反馈,除了没有看到标签出现。

1 个答案:

答案 0 :(得分:1)

我在您的代码中看到的主要问题:

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

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

  3. 以下是重构代码:

    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. 我向title添加了名为ABSegment的计算属性,您可以随时使用title更改mysegment.title = "new title"
    3. 我将init(withSegment:)变成了便利初始。它调用标准init(frame:),然后设置title。使用withProperty并不常见,因此我对其进行了更改。您可以使用ABSegment创建var segment = ABSegment(title: "some title")
    4. required init?(coder aDecoder: NSCoder)致电setupLabel,以便ABSegment可以与 Storyboard 中添加的视图一起使用。
    5. 保留layoutSubviewsupdateConstraints的覆盖以记录帧。这就是他们现在所做的一切。