【问题标题】:Swift - vertical stack elements overlaying each otherSwift - 相互重叠的垂直堆栈元素
【发布时间】:2020-07-26 07:01:59
【问题描述】:

我很难使用 UIStackView 获得所需的效果。这是我的设置:

带有文本字段的字段元素:

class NewEditableFuelSheetField: UIView {
    
    var titleText: String?
    
    var textFieldText: String?
    
    init(titleText: String, textFieldText: String) {
        
        super.init(frame: .zero)
        self.titleText = titleText
        self.textFieldText = textFieldText
        
        self.addSubview(editableField)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private lazy var editableField: UIStackView = {
        let title = UILabel()
        title.text = self.titleText
        
        let textField = UITextField()
        textField.isEnabled = false
        
        let stack = UIStackView(arrangedSubviews: [title, textField])
        stack.axis = .vertical
        stack.distribution = .fillEqually
        stack.translatesAutoresizingMaskIntoConstraints = false
        
        return stack
    }()
    
    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
        editableField.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}

具有固定值的字段:

class NewFixedFuelSheetField: UIView {
    
    var title: String?
    
    var detail: String?
    
    init(title: String, detail: String) {
        
        super.init(frame: .zero)
        
        self.title = title
        
        self.detail = detail
        configureAutoLayout()
        
        self.addSubview(fixedField)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private lazy var fixedField: UIStackView = {
        
        let title = UILabel()
        let detail = UILabel()
        
        title.text = self.title
        detail.text = self.detail
        
        let stack = UIStackView(arrangedSubviews: [title, detail])
        stack.axis = .vertical
        stack.distribution = .fillEqually
        
        stack.translatesAutoresizingMaskIntoConstraints = false
        
        self.addSubview(stack)
        
        return stack
    }()
    
    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
        fixedField.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}

包含一堆不可编辑字段的标题视图:

class NewFuelSheetHeaderView: UIView {
    
    // MARK:  Init

    override init(frame: CGRect) {
        super.init(frame: .zero)
        self.addSubview(fuelSheetHeaderStack)
        configureAutoLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK:  Properties

    // 'detail' text will be brought in from API in next ticket
    
    private lazy var flightNumber: NewFixedFuelSheetField = {
        return NewFixedFuelSheetField(title: "Flight number", detail: "VS0101")
    }()
    
    private lazy var aircraftReg: NewFixedFuelSheetField = {
        return NewFixedFuelSheetField(title: "Aircraft reg", detail: "GAAAA")
    }()
    
    private lazy var date: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Date", detail: "01.01.21")
    }()
    
    private lazy var time: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Time", detail: "12:01")
    }()
    
    private let supplier: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Supplier", detail: "i6Staging, BAPCO")
    }()
    
    private let fuelGrade: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Fuel grade", detail: "Jet A")
    }()
    
    private let freezePoint: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Freeze point", detail: "-40")
    }()
    
    private let specificGravity: NewFixedFuelSheetField = {
       return NewFixedFuelSheetField(title: "Specific gravity", detail: "0.793")
    }()
    
    private lazy var fuelSheetHeaderFirstRow: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [
            flightNumber,
            aircraftReg,
            date,
            time
        ])
        
        stack.axis = .horizontal
        stack.distribution = .fillEqually
        return stack
    }()
    
    private lazy var fuelSheetHeaderSecondRow: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [
            supplier,
            fuelGrade,
            freezePoint,
            specificGravity
        ])
        
        stack.axis = .horizontal
        stack.distribution = .fillEqually
        return stack
    }()
    
    private lazy var fuelSheetHeaderStack: UIStackView = {
       let stack = UIStackView(arrangedSubviews: [fuelSheetHeaderFirstRow, fuelSheetHeaderSecondRow])
        stack.axis = .vertical
        stack.distribution = .fillEqually
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    // MARK:  Configuration
    
    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
            fuelSheetHeaderStack.topAnchor.constraint(equalTo: self.topAnchor, constant: 50),
            fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
            fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
            fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
        ])
    }
}

最终需要放置在标题下方的第二个视图:

class NewFuelSheetRefuelInfoView: UIView {

    // MARK:  Init
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
        self.addSubview(refuelStackView)
        configureAutoLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK:  Properties
    
    private lazy var preRefuel: NewEditableFuelSheetField = {
        return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
    }()
    
    private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
        return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
    }()
    
    private lazy var requiredUplift: NewEditableFuelSheetField = {
        return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
    }()
    
    private lazy var actualUplift: NewEditableFuelSheetField = {
        return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
    }()
    
    private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
        return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
    }()
    
    private lazy var refuelStackView: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [
            preRefuel,
            requiredDepartureFuel,
            requiredUplift,
            actualUplift,
            actualDepartureFuel
        ])
        
        stack.axis = .vertical
        stack.distribution = .fillEqually
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    // MARK:  Config

    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
            refuelStackView.topAnchor.constraint(equalTo: topAnchor, constant: 50),
            refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
            refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
            refuelStackView.heightAnchor.constraint(equalToConstant: 300)
        ])
    }
}

然后我有一个主视图将这些元素组合在一起:

class NewFuelSheetMainView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
        self.addSubview(mainStack)
        configureAutoLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private lazy var flightDetailsHeader: NewFuelSheetHeaderView = {
       return NewFuelSheetHeaderView()
    }()
    
    private lazy var refuelView: NewFuelSheetRefuelInfoView = {
        return NewFuelSheetRefuelInfoView()
    }()
    
    private lazy var mainStack: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [flightDetailsHeader, refuelView])
        stack.axis = .vertical
        stack.distribution = .fillEqually
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()

    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
            mainStack.topAnchor.constraint(equalTo: topAnchor, constant: 30),
            mainStack.leftAnchor.constraint(equalTo: leftAnchor),
            mainStack.rightAnchor.constraint(equalTo: rightAnchor),
            mainStack.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

最后是一个 VC 来显示主视图:

class DataEntryViewController: I6ViewController {
    
    // MARK:  Init

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nil, bundle: nil)
        configureAutoLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK:  Lifecycle

    override func viewDidLoad() {
        view.backgroundColor = .white
    }
    
    // MARK:  Properties

    private lazy var mainView: NewFuelSheetMainView = {
        let mainView = NewFuelSheetMainView()
        mainView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(mainView)
        return mainView
    }()
    
    // MARK:  Configuration
    
    private func configureAutoLayout() {
        NSLayoutConstraint.activate([
            mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
            mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
        ])
    } 
}

在我看来,(显然我的逻辑是有缺陷的,因为它不起作用!!)这里的关键部分是在我展示两个较小视图的堆栈的主视图中。在这里,我清楚地将堆栈设置为 .vertical 并将这个垂直堆栈固定在主视图的顶部和底部。然而,第二个视图并没有出现在我所期望的第一个视图之下,它们只是出现在另一个之上:

很明显,我在这里遗漏了一个关键点,但我看不到在哪里。任何帮助将不胜感激。

【问题讨论】:

    标签: swift xcode autolayout vertical-alignment uistackview


    【解决方案1】:

    您可能猜到这与您的约束有关。这就是您的视图当前的样子。 UIstackViews 可以根据内容自行调整大小。需要给 UIView 提供高度和宽度信息。

    这是在我进行了调整之后。

    DataEntryViewController
    mainView.topAnchor.constraint(equalTo: view.topAnchor),
    mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
    mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
    mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    
    NewFuelSheetMainView
    mainStack.topAnchor.constraint(equalTo: view.topAnchor),
    mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
    mainStack.rightAnchor.constraint(equalTo: view.rightAnchor),
    mainStack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    
    NewFuelSheetRefuelInfoView
    refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
    refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
    refuelStackView.heightAnchor.constraint(equalToConstant: 300)
    
    NewFuelSheetHeaderView
    fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
    fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
    fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
    

    【讨论】:

      【解决方案2】:

      开发过程中的一些提示:

      • 为您的 UI 元素提供对比背景色,以便在运行时轻松查看帧
      • 一次处理一个元素
      • 为所有UIView 子类设置self.clipsToBounds = true - 如果它们的子视图不可见,则说明存在约束问题

      例如,让我们从您的NewEditableFuelSheetField开始...

      创建一个开发/临时视图控制器:

      class ScratchViewController: UIViewController {
          
          override func viewDidLoad() {
              view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
              
              let v = NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
              view.addSubview(v)
              v.translatesAutoresizingMaskIntoConstraints = false
              
              // respect safe-area
              let g = view.safeAreaLayoutGuide
              NSLayoutConstraint.activate([
                  v.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                  v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                  v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
              ])
              
          }
      
      }
      

      NO 更改您的 NewEditableFuelSheetField 类,输出如下:

      文本字段在那里,但我们不知道从查看输出。因此,让我们对您的课程进行一些更改:

      class NewEditableFuelSheetField: UIView {
          
          var titleText: String?
          
          var textFieldText: String?
          
          init(titleText: String, textFieldText: String) {
              
              super.init(frame: .zero)
              self.titleText = titleText
              self.textFieldText = textFieldText
              
              self.addSubview(editableField)
              
              // this was missing from the code in your question
              configureAutoLayout()
      
              // so we can see the frame at run-time
              self.backgroundColor = .red
          }
          
          required init?(coder: NSCoder) {
              fatalError("init(coder:) has not been implemented")
          }
          
          private lazy var editableField: UIStackView = {
              let title = UILabel()
              title.text = self.titleText
              
              let textField = UITextField()
              textField.isEnabled = false
              
              // so we can see the frames at run-time
              title.backgroundColor = .yellow
              textField.backgroundColor = .green
              //
          
              let stack = UIStackView(arrangedSubviews: [title, textField])
              stack.axis = .vertical
              stack.distribution = .fillEqually
              stack.translatesAutoresizingMaskIntoConstraints = false
              
              return stack
          }()
          
          private func configureAutoLayout() {
              NSLayoutConstraint.activate([
                  editableField.heightAnchor.constraint(equalToConstant: 50)
              ])
          }
      }
      

      好的...现在我们看到了标签和字段的框架...但是我们将视图的前导/尾随限制为每边 20 点。那么,为什么我们看不到红色视图背景呢?

      让我们在init 中添加clipsToBounds

          self.addSubview(editableField)
          
          // this was missing from the code in your question
          configureAutoLayout()
          
          // so we can see the frames at run-time
          self.backgroundColor = .red
          
          // set clipsToBounds
          self.clipsToBounds = true
      

      新的输出:

      嗯...显然不是我们想要的。如果我们使用Debug View Hierarchy,我们可以看到NewEditableFuelSheetField 的实例的高度和宽度为零,并且其内容显示“越界”。

      您已将标签和字段添加到垂直堆栈视图,将该堆栈视图添加到 self,并将其高度设置为 50...但是您没有为堆栈视图提供任何相对约束到它的superview。

      让我们解决这个问题:

      private func configureAutoLayout() {
          NSLayoutConstraint.activate([
              editableField.heightAnchor.constraint(equalToConstant: 50),
              
              // constraints relative to superview (self)
              editableField.topAnchor.constraint(equalTo: topAnchor),
              editableField.leadingAnchor.constraint(equalTo: leadingAnchor),
              editableField.bottomAnchor.constraint(equalTo: bottomAnchor),
          ])
      }
      

      呜呼!看起来我们已经取得了一些进展。

      现在让我们将 5 个NewEditableFuelSheetField 实例添加到垂直堆栈视图(间距为 8 以使其清晰):

      class ScratchViewController: UIViewController {
          
          private lazy var preRefuel: NewEditableFuelSheetField = {
              return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
          }()
          
          private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
              return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
          }()
          
          private lazy var requiredUplift: NewEditableFuelSheetField = {
              return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
          }()
          
          private lazy var actualUplift: NewEditableFuelSheetField = {
              return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
          }()
          
          private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
              return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
          }()
          
          private lazy var refuelStackView: UIStackView = {
              let stack = UIStackView(arrangedSubviews: [
                  preRefuel,
                  requiredDepartureFuel,
                  requiredUplift,
                  actualUplift,
                  actualDepartureFuel
              ])
              
              stack.axis = .vertical
              stack.distribution = .fillEqually
              stack.translatesAutoresizingMaskIntoConstraints = false
              return stack
          }()
          
          override func viewDidLoad() {
              view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
              
              view.addSubview(refuelStackView)
              
              // for visual example
              refuelStackView.spacing = 8
              
              // respect safe-area
              let g = view.safeAreaLayoutGuide
              NSLayoutConstraint.activate([
                  refuelStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                  refuelStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                  refuelStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
              ])
              
          }
      
      }
      

      结果(我添加了一个深蓝色的虚线轮廓来显示堆栈视图的框架):

      如果您对每个 UIView 子类(从最“内部”的视图开始)遵循该开发过程,那么您应该已经开始了。


      附带说明:在给要添加到堆栈视图(分别为垂直/水平)的视图提供大小(高度或宽度)约束时要小心,然后还要给堆栈视图.distribution = .fillEqually AND它自己的高度/宽度约束。你可以说:

      make each of 5 arranged subviews 50-pts in height
      make the stack view 300-pts in height
      fill equally
      

      你会得到5 * 50 = 250 ...这将与stack view height = 300冲突

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-11-26
        • 1970-01-01
        • 2017-05-23
        • 2015-04-11
        • 1970-01-01
        • 2011-06-05
        • 2011-01-16
        • 2023-01-10
        相关资源
        最近更新 更多