你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS9-by-Tutorials-學習筆記六:UIStackView-Auto-Layout-Changes

iOS9-by-Tutorials-學習筆記六:UIStackView-Auto-Layout-Changes

編輯:IOS開發綜合

iOS9-by-Tutorials-學習筆記六:UIStackView-Auto-Layout-Changes

今天這是第六篇筆記,現在回過頭去看,我也沒有想到自己能夠更新到第六篇。我算是一個比較懶的人了,現在已經不太喜歡動手敲代碼了。在寫這幾篇筆記的時候,我需要一邊看英文的文檔,一邊測試代碼,還得考慮怎麼能夠寫明白。這裡有點說明,我的英語水平只是四級,語文水平只能用呵呵評價了,文章中的語句難免會有不通順的地方,希望能夠把語義表述清楚。

閒話多了,回到正題,這篇文章介紹UIStackView和一些Auto Layout的改變。

UIStackView我個人理解是為了解決使用Storyboard添加的約束需要經常變化的情況。我想我們可能都在開發中遇到過修改約束的情況,一般是把約束與一個outlet的約束link起來,然後代碼修改,但是這個操作起來是不方便的。UIStackView通過修改一些簡單的屬性,例如alignment, distribution, and spacing,從而讓UIStackView根據我們的修改自動調整內部的顯示。

Auto Layout的改變主要是介紹layout anchors和layout guides。

Getting Started

打開本章的配套的工程VacationSpots,在iPhone 6模擬器上運行,能夠看到APP有一些UI的問題,不要擔心,在後面將會修復這些問題。簡單梳理一下問題如下:
1. 圖中標出的內容沒有在垂直方向居中
\

點擊列表中的London Cell進入詳情頁面,最下面的三個按鈕沒有平均分配空間:

點擊WEATHER旁邊的hide按鈕,內容是被隱藏了,但是留下了一塊空白,下面的內容沒有移動上來:

WHAT TO SEE 部分在WHY VISIT的下面會更加合理一點。

現在已經了解了這些問題,下面開始用UIStackView來修改這些問題。打開Main.storyboard,查看如下的Controller scene:

能夠注意到上面的每個對應的控件都有背景顏色,這個只是為了幫助我們查看這些屬性的變化。這些背景顏色在運行的時候都會被去掉,通過如下代碼,如果你想讓它們在運行的時候也顯示注釋掉這些代碼就可以:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjwvYmxvY2txdW90ZT4NCjxwcmUgY2xhc3M9"brush:java;"> // SpotInfoViewController.swift override func viewDidLoad() { super.viewDidLoad() // Clear background colors from labels and buttons for view in backgroundColoredViews { view.backgroundColor = UIColor.clearColor() } ........ }

在Storyboard中的的控件都通過outlet與SpotInfoViewController.swift中對應的屬性進行了關聯。在storyboard中顯示的名字對應SpotInfoViewController.swift對應的變量。

Your first stack view

我們先用Stack View解決我們問題列表中底部按鈕的問題。使用UIStackView能夠在一個坐標抽上分配位置和控件之間的空間。幸運的是將Views嵌入到UIStackView中並沒有太難。在Storyboard中的Spot Info View Controller選擇如下三個按鈕控件:
\

選擇好三個按鈕後,在Storyboard中點擊如下的按鈕:

當Views被嵌入UIStackView之後,Views的約束都被移除了,同時需要設置UIStackView的約束。選中UIStackView,然後按照如圖添加約束:

這裡有個選擇UIStackView的技巧,由於UIStackView是在按鈕的後面,很不好選中,我們可以按住Shift,右擊,在出現的菜單中列出來當前點擊位置所有的View,我們可以選擇UIstackView。另一種方法我們可以在outline view中選擇。

設置好約束後,能夠看到按鈕顯示如下,第一個按鈕被拉伸了,填充滿了UIStackView的剩余空間。UIstackView有一個Distribution屬性,用於控制Views怎麼在UIStackView中顯示,現在設置的是Fill,即將會填充滿UIStackView。為了這個目的,UIstackView將會根據View的ccontent hugging優先級去拉伸View,最低的將會被拉伸。如果優先級一樣,將會拉伸第一個。

我們的目的是讓View間的距離相等,在Attributes inspector中修改Distribution為Equal Spacing。

運行APP,能夠看到我們的按鈕顯示正確了:

一些思考

思考一下你使用Auto Layout,通過約束實現上面的要求,那將是一種什麼令人”愉悅”的行為。可能你很熟悉Auto Layout,認為這些東西都很簡單,那麼你在考慮一下如果我們後面有需求添加一個按鈕,刪除一個按鈕呢,怎麼辦呢?約束刪除了重新添加嗎?如果使用UIStackView,這些將變得比較簡單,只要我們添加或者刪除View,其他的工作UIstackView就會幫我們做了。

UISTackView的更加深入的講解,將會在下一篇文章中繼續介紹。這裡先介紹一下Auto Layout的新特性:layout anchors和layout guides。

Layout anchors

Layout anchors提供了我們一種簡單的創建約束的方式。

想象一下我們在iOS 9之前創建一個約束,簡直就是天書,但是在iOS 9中使用Layout anchors將會簡單好多,下面是兩種的對比:

// iOS9以前
let constraint = NSLayoutConstraint(item: topLabel, attribute: .Bottom, relatedBy: .Equal, toItem: bottomLabel, attribute: .Top, multiplier: 1, constant: 8)

// iOS 9
let constraint = topLabel.bottomAnchor.constraintEqualToAnchor(bottomLabel.topAnchor, constant: 8)

Layout anchors不僅理解起來簡單,而且寫起來也簡單了。

對應於我們在iOS 9以前添加約束時候的attribute,基本都有與之對應的anchor,例如top對應topAnchor,bottom對應bottomAnchor等。Layout Anchor都是直接或者間接繼承自NSLayoutAnchor,上面只是演示了一下相等的情況,我們都知道在約束中又大於小於等,下面列出NSLayoutAnchor的接口文件,從接口文件中能夠清楚的了解到對應的方法:

import Foundation
import UIKit

/*  NSLayoutAnchor.h
    Copyright (c) 2015, Apple Inc. All rights reserved.
*/

/* An NSLayoutAnchor represents an edge or dimension of a layout item.  Its concrete 
 subclasses allow concise creation of constraints.  
    Instead of invoking 

 +[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:] 

 directly, you can instead do something like this:

 [myView.topAnchor constraintEqualToAnchor:otherView.topAnchor constant:10];

 The -constraint* methods are available in multiple flavors to support use of different
 relations and omission of unused options.
 */

@available(iOS 9.0, *)
public class NSLayoutAnchor : NSObject {

    /* These methods return an inactive constraint of the form thisAnchor = otherAnchor.
     */
    public func constraintEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!
    public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!
    public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!

    /* These methods return an inactive constraint of the form thisAnchor = otherAnchor + constant.
     */
    public func constraintEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
    public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
    public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
}

/* Axis-specific subclasses for location anchors: top/bottom, leading/trailing, baseline, etc.
 */

@available(iOS 9.0, *)
public class NSLayoutXAxisAnchor : NSLayoutAnchor {
}

@available(iOS 9.0, *)
public class NSLayoutYAxisAnchor : NSLayoutAnchor {
}

/* This layout anchor subclass is used for sizes (width & height).
 */

@available(iOS 9.0, *)
public class NSLayoutDimension : NSLayoutAnchor {

    /* These methods return an inactive constraint of the form 
        thisVariable = constant.
    */
    public func constraintEqualToConstant(c: CGFloat) -> NSLayoutConstraint!
    public func constraintGreaterThanOrEqualToConstant(c: CGFloat) -> NSLayoutConstraint!
    public func constraintLessThanOrEqualToConstant(c: CGFloat) -> NSLayoutConstraint!

    /* These methods return an inactive constraint of the form 
        thisAnchor = otherAnchor * multiplier.
    */
    public func constraintEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!
    public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!
    public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!

    /* These methods return an inactive constraint of the form 
        thisAnchor = otherAnchor * multiplier + constant.
    */
    public func constraintEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
    public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
    public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
}

在上面的接口文件中,我們能夠清楚的了解到NSLayoutAnchor有三個子類:NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension。下面列出了UIView的Anchor都是對應的那種類型:

extension UIView {
    /* Constraint creation conveniences. See NSLayoutAnchor.h for details.
     */
    @available(iOS 9.0, *)
    public var leadingAnchor: NSLayoutXAxisAnchor { get }
    @available(iOS 9.0, *)
    public var trailingAnchor: NSLayoutXAxisAnchor { get }
    @available(iOS 9.0, *)
    public var leftAnchor: NSLayoutXAxisAnchor { get }
    @available(iOS 9.0, *)
    public var rightAnchor: NSLayoutXAxisAnchor { get }
    @available(iOS 9.0, *)
    public var centerXAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    public var topAnchor: NSLayoutYAxisAnchor { get }
    @available(iOS 9.0, *)
    public var bottomAnchor: NSLayoutYAxisAnchor { get }
    @available(iOS 9.0, *)
    public var firstBaselineAnchor: NSLayoutYAxisAnchor { get }
    @available(iOS 9.0, *)
    public var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
    @available(iOS 9.0, *)
    public var centerYAnchor: NSLayoutYAxisAnchor { get }

    @available(iOS 9.0, *)
    public var widthAnchor: NSLayoutDimension { get }
    @available(iOS 9.0, *)
    public var heightAnchor: NSLayoutDimension { get }
}

上面UIView的Anchor屬性被分成了三類,同樣我們在設置屬性的時候,也要求同類的屬性的才能設置,比如ViewA和ViewB之間的約束,ViewA-topAnchor和ViewB-bottomAnchor是可以的,ViewA-topAnchor和ViewB-leftAnchor就是不允許的,如果這樣的話編譯器會警告,運行時也會報錯。

注意: whyVisitLabel.topAnchor.constraintEqualToAnchor(whatToSeeLabel.leftAnchor) 這個按照上面的說法應該會報錯的,但是我在運行的時候也沒有報錯,可能是我這裡只是隨便寫出一個做測試的原因,後續我會繼續試驗一下這個知識點,然後再改正。

Layout guides

有時候我們想設置兩個View之間的空間,需要在兩個View之間添加一個不可見的View(dummy view),然後在設置約束。Layout guide可以理解為一個隱形的不可見View,我們能夠使用它的矩形邊緣來布局,我們可以像我們使用View一樣設置約束。使用Layout guide的好處是輕量,而且不會在view的層級中,也不會參與事件的響應過程。layout guide也包含除了firstBaselineAnchor和lastBaselineAnchor之外的View所有的Anchor。

Fixing the alignment bug

下面就利用layout guide來修復列表頁面文字內容上下不居中的問題。看下圖是我們設置的約束,我們設置label距離上面的距離是15,當下面的label顯示一行的時候是正常的,如果要是兩行了,由於上面的約束是固定的,最終就變成了不居中的效果了。
\

在iOS 9之前,我們想解決這個問題可以把兩個label放置到一個container view容器中,設置這個container view為劇中,這裡面的container view就是不可見的view即dummy view。現在在iOS 9上我們可以使用layout guide來代替這個view。

目前只能通過代碼來添加layout guide。打開VacationSpotCell.swift文件,修改對應代碼:

override func awakeFromNib() {
    super.awakeFromNib()

    // TODO: Add layoutGuide code here to center the name and locationName labels vertically

    // 創建layou guide
    let layoutGuide = UILayoutGuide()
    contentView.addLayoutGuide(layoutGuide)

    // 設置layout guide的約束
    let topConstraint = layoutGuide.topAnchor.constraintEqualToAnchor(nameLabel.topAnchor)
    let bottomConstraint = layoutGuide.bottomAnchor.constraintEqualToAnchor(locationNameLabel.bottomAnchor)
    let centerConstraint = layoutGuide.centerYAnchor.constraintEqualToAnchor(contentView.centerYAnchor)

    // 激活layout guide的約束
    NSLayoutConstraint.activateConstraints([topConstraint, bottomConstraint, centerConstraint])
  }

運行APP發現,部分文字被截斷了:
\

處理截斷問題

這個原因是我們設置layout guide居中,但是nameLabel(上面的)與super view top的約束還存在,造成了下面的label被擠壓了,這時候我們只要刪除掉這個約束就可以了。但是刪除了之後storyboard會提示錯誤,這時候我們可以使用占位約束,這個主要是為了使storyboard不報錯,在運行的時候並不會使用。
\

最近一直在加班,斷斷續續整理了好久終於整理完了這篇文章了,可能會有錯誤,還請大家指出。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved