2015-10-17 112 views
12

的的iOS 9.0自帶UIStackView這使得它更容易根據自己的內容大小的佈局圖。例如,要按照其內容寬度將3個按鈕放在一行中,您可以簡單地將它們嵌入到堆棧視圖中,按比例設置軸水平和分佈 - 填充。模仿UIStackView`填補IOS版本proportionally`佈局方法之前9.0

Stack View Solution

的問題是如何實現在不支持堆疊視圖舊的IOS版本相同的結果。我想出了

一種解決方案是粗糙,看起來並不好。再次,您將3個按鈕放在一行中,並使用約束將它們固定到最近的鄰居。這樣做之後,您顯然會看到內容優先模糊性錯誤,因爲自動佈局系統不知道哪個按鈕需要在別人之前增長/縮小。

Content Priority Ambiguity

不幸的是,標題是應用啓動之前,不明所以,你就可能隨心所欲挑選一個按鈕。比方說,我將中間按鈕的水平內容從標準250減少到249,現在它會在其他兩個之前增長。另一個問題是左右按鈕嚴格縮小到它們的內容寬度,沒有像Stack View版本那樣的漂亮的填充。

Layout Comparsion

回答

3

是的,我們可以得到使用唯一的限制:)

source code

想象同樣的結果,我有三個標籤:

  1. firstLabel具有固有內容大小等於(62.5,40)
  2. secondLabel具有固有內容大小等於(170.5,40)
  3. thirdLabel具有固有內容大小等於(54,40)

Strucuture

-- ParentView -- 
    -- UIView -- (replace here the UIStackView) 
     -- Label 1 -- 
     -- Label 2 -- 
     -- Label 3 --    

約束

例如UIView有這個約束: view.leading = superview.leading,view.trailing = superview。拖尾,並且其垂直居中

enter image description here

UILabels約束

enter image description here

SecondLabel.width等於:

firstLabel.width *(secondLabelIntrinsicSizeWidth/firstLabelIntrinsicSizeWidth )

ThirdLabel.width等於:

firstLabel.width *(thirdLabelIntrinsicSizeWidth/firstLabelIntrinsicSizeWidth)

我將備份更多的解釋

enter image description here

5

這似乎過於複雜了這樣一個簡單的事情。但是約束的乘數價值是隻讀的,所以你必須付出艱辛的努力。

我會做這樣的,如果我不得不:

  1. 在IB:創建約束的UIView的水平方向上填滿了上海華(例如)

  2. 在IB:添加3按鈕,添加約束來水平對齊它們。

  3. 在代碼中:以編程方式在每個UIButton和UIView之間創建1個NSConstraint,屬性爲NSLayoutAttributeWidth,乘數爲0.33。

在這裏,您將獲得3個使用1/3 UIView寬度的相同寬度的按鈕。

  1. 觀察按鈕的title(使用KVO或UIButton的子類)。 當標題變化,像計算你的按鈕,內容的大小:

    CGSize stringsize = [myButton.title sizeWithAttributes: @{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];

  2. 刪除所有程序創建的約束。

  3. 比較所計算出的寬度(在步驟4)用的UIView的寬度各按鈕的和確定每個按鈕的比率。

  4. 重新創建步驟3中相同的方式的限制,但用0.33通過在步驟6中計算出的比率和將它們添加到UI元素。

2

你可能要考慮一個UIStackView的backport,有幾個開源項目。這樣做的好處是,最終如果移動到UIStackView,您將只需最少的代碼更改。我已經使用TZStackView,它的工作令人欽佩。

或者,更輕的解決方案是隻複製比例堆棧視圖佈局的邏輯。在您的堆棧

  • 設置每個的寬度的觀點

    1. 計算總固有內容寬度查看等於乘以其總含量固有寬度的比例父堆棧視圖。

      我附上了一個水平比例堆棧視圖的粗略示例,您可以在Swift Playground中運行它。

    import UIKit 
    import XCPlayground 
    
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) 
    
    view.layer.borderWidth = 1 
    view.layer.borderColor = UIColor.grayColor().CGColor 
    view.backgroundColor = UIColor.whiteColor() 
    
    XCPlaygroundPage.currentPage.liveView = view 
    
    class ProportionalStackView: UIView { 
    
        private var stackViewConstraints = [NSLayoutConstraint]() 
    
        var arrangedSubviews: [UIView] { 
        didSet { 
         addArrangedSubviews() 
         setNeedsUpdateConstraints() 
        } 
        } 
    
        init(arrangedSubviews: [UIView]) { 
        self.arrangedSubviews = arrangedSubviews 
        super.init(frame: CGRectZero) 
        addArrangedSubviews() 
        } 
    
        convenience init() { 
        self.init(arrangedSubviews: []) 
        } 
    
        required init?(coder aDecoder: NSCoder) { 
        fatalError("init(coder:) has not been implemented") 
        } 
    
        override func updateConstraints() { 
        removeConstraints(stackViewConstraints) 
        var newConstraints = [NSLayoutConstraint]() 
    
        for (n, subview) in arrangedSubviews.enumerate() { 
    
         newConstraints += buildVerticalConstraintsForSubview(subview) 
    
         if n == 0 { 
         newConstraints += buildLeadingConstraintsForLeadingSubview(subview) 
         } else { 
         newConstraints += buildConstraintsBetweenSubviews(arrangedSubviews[n-1], subviewB: subview) 
         } 
    
         if n == arrangedSubviews.count - 1 { 
         newConstraints += buildTrailingConstraintsForTrailingSubview(subview) 
         } 
    
        } 
    
        // for proportional widths, need to determine contribution of each subview to total content width 
        let totalIntrinsicWidth = subviews.reduce(0) { $0 + $1.intrinsicContentSize().width } 
    
        for subview in arrangedSubviews { 
         let percentIntrinsicWidth = subview.intrinsicContentSize().width/totalIntrinsicWidth 
         newConstraints.append(NSLayoutConstraint(item: subview, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: percentIntrinsicWidth, constant: 0)) 
        } 
    
        addConstraints(newConstraints) 
        stackViewConstraints = newConstraints 
    
        super.updateConstraints() 
        } 
    
    } 
    
    // Helper methods 
    extension ProportionalStackView { 
    
        private func addArrangedSubviews() { 
        for subview in arrangedSubviews { 
         if subview.superview != self { 
         subview.removeFromSuperview() 
         addSubview(subview) 
         } 
        } 
        } 
    
        private func buildVerticalConstraintsForSubview(subview: UIView) -> [NSLayoutConstraint] { 
        return NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview]) 
        } 
    
        private func buildLeadingConstraintsForLeadingSubview(subview: UIView) -> [NSLayoutConstraint] { 
        return NSLayoutConstraint.constraintsWithVisualFormat("|-0-[subview]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview]) 
        } 
    
        private func buildConstraintsBetweenSubviews(subviewA: UIView, subviewB: UIView) -> [NSLayoutConstraint] { 
        return NSLayoutConstraint.constraintsWithVisualFormat("[subviewA]-0-[subviewB]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subviewA": subviewA, "subviewB": subviewB]) 
        } 
    
        private func buildTrailingConstraintsForTrailingSubview(subview: UIView) -> [NSLayoutConstraint] { 
        return NSLayoutConstraint.constraintsWithVisualFormat("[subview]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview]) 
        } 
    
    } 
    
    let labelA = UILabel() 
    labelA.text = "Foo" 
    
    let labelB = UILabel() 
    labelB.text = "FooBar" 
    
    let labelC = UILabel() 
    labelC.text = "FooBarBaz" 
    
    let stack = ProportionalStackView(arrangedSubviews: [labelA, labelB, labelC]) 
    
    stack.translatesAutoresizingMaskIntoConstraints = false 
    labelA.translatesAutoresizingMaskIntoConstraints = false 
    labelB.translatesAutoresizingMaskIntoConstraints = false 
    labelC.translatesAutoresizingMaskIntoConstraints = false 
    labelA.backgroundColor = UIColor.orangeColor() 
    labelB.backgroundColor = UIColor.greenColor() 
    labelC.backgroundColor = UIColor.redColor() 
    
    view.addSubview(stack) 
    view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-0-[stack]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["stack": stack])) 
    view.addConstraint(NSLayoutConstraint(item: stack, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)) 
    
  • 2

    使用自動佈局,你的優勢。它可以爲你完成所有繁重的工作。

    這是一個UIViewController,它列出了3 UILabels,就像你在屏幕截圖中一樣,沒有任何計算。有3 UIView子視圖用於給標籤「填充」並設置背景顏色。其中每個UIViews都有一個UILabel子視圖,它只顯示文本而沒有其他內容。

    所有的佈局都在viewDidLoad的自動佈局中完成,這意味着沒有計算比率或幀,也沒有KVO。改變諸如填充和壓縮/擁抱優先級之類的事情是輕而易舉的。這也可能避免依賴像TZStackView這樣的開源解決方案。這與在界面生成器中完全一樣簡單,完全不需要代碼。

    class StackViewController: UIViewController { 
    
        private let leftView: UIView = { 
         let leftView = UIView() 
         leftView.translatesAutoresizingMaskIntoConstraints = false 
         leftView.backgroundColor = .blueColor() 
         return leftView 
        }() 
    
        private let leftLabel: UILabel = { 
         let leftLabel = UILabel() 
         leftLabel.translatesAutoresizingMaskIntoConstraints = false 
         leftLabel.textColor = .whiteColor() 
         leftLabel.text = "A medium title" 
         leftLabel.textAlignment = .Center 
         return leftLabel 
        }() 
    
        private let middleView: UIView = { 
         let middleView = UIView() 
         middleView.translatesAutoresizingMaskIntoConstraints = false 
         middleView.backgroundColor = .redColor() 
         return middleView 
        }() 
    
        private let middleLabel: UILabel = { 
         let middleLabel = UILabel() 
         middleLabel.translatesAutoresizingMaskIntoConstraints = false 
         middleLabel.textColor = .whiteColor() 
         middleLabel.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" 
         middleLabel.textAlignment = .Center 
         return middleLabel 
        }() 
    
        private let rightView: UIView = { 
         let rightView = UIView() 
         rightView.translatesAutoresizingMaskIntoConstraints = false 
         rightView.backgroundColor = .greenColor() 
         return rightView 
        }() 
    
        private let rightLabel: UILabel = { 
         let rightLabel = UILabel() 
         rightLabel.translatesAutoresizingMaskIntoConstraints = false 
         rightLabel.textColor = .whiteColor() 
         rightLabel.text = "OK" 
         rightLabel.textAlignment = .Center 
         return rightLabel 
        }() 
    
    
        override func viewDidLoad() { 
         super.viewDidLoad() 
    
         view.addSubview(leftView) 
         view.addSubview(middleView) 
         view.addSubview(rightView) 
    
         leftView.addSubview(leftLabel) 
         middleView.addSubview(middleLabel) 
         rightView.addSubview(rightLabel) 
    
         let views: [String : AnyObject] = [ 
          "topLayoutGuide" : topLayoutGuide, 
          "leftView" : leftView, 
          "leftLabel" : leftLabel, 
          "middleView" : middleView, 
          "middleLabel" : middleLabel, 
          "rightView" : rightView, 
          "rightLabel" : rightLabel 
         ] 
    
         // Horizontal padding for UILabels inside their respective UIViews 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[leftLabel]-(16)-|", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[middleLabel]-(16)-|", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[rightLabel]-(16)-|", options: [], metrics: nil, views: views)) 
    
         // Vertical padding for UILabels inside their respective UIViews 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[leftLabel]-(6)-|", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[middleLabel]-(6)-|", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[rightLabel]-(6)-|", options: [], metrics: nil, views: views)) 
    
         // Set the views' vertical position. The height can be determined from the label's intrinsic content size, so you only need to specify a y position to layout from. In this case, we specified the top of the screen. 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][leftView]", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][middleView]", options: [], metrics: nil, views: views)) 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][rightView]", options: [], metrics: nil, views: views)) 
    
         // Horizontal layout of views 
         NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[leftView][middleView][rightView]|", options: [], metrics: nil, views: views)) 
    
         // Make sure the middle view is the view that expands to fill up the extra space 
         middleLabel.setContentHuggingPriority(UILayoutPriorityDefaultLow, forAxis: .Horizontal) 
         middleView.setContentHuggingPriority(UILayoutPriorityDefaultLow, forAxis: .Horizontal) 
        } 
    
    } 
    

    結果視圖: