1、Views

概述

UIView或者它的子類知道怎樣將自己繪制在一個矩形區(qū)域中。我們app所有可視的的界面來自于視圖。創(chuàng)建和配置一個view非常的簡單。比如你可以在Xib編輯器中拖一個UIButton或者其他view到一個View上,你也可以使用代碼來操作所有的繪制。你可以控制view顯示和消失、移動、改變大小或者在它上面顯示其他view、給它做動畫等。

UIView是UIResponder的子類,所以也能相應(yīng)事件(和用戶交互,這也是UIView和CALayer本質(zhì)區(qū)別)。

視圖層次是視圖組織的主要模式。一個view可以擁有很多子視圖,一個子視圖只能擁有一個直接父視圖,這樣就形成一個視圖的層級樹。如果一個視圖從界面上remove,它所有子視圖也會被remove掉。如果一個view隱藏(hidden)它所有子視圖hidden。其他變化同樣也會共享給他的子視圖。

我們應(yīng)該選擇xib還是code創(chuàng)建視圖,兩者沒有好壞這分,這取決于你的需求、習(xí)慣和你的app的整體架構(gòu)。

Window

app的window是視圖層級最頂部的view。它是一個UIWindow對象(或者UIWindow的子類),UIWindow是UIView的子類。我們app擁有一個主window,在app運(yùn)行期間創(chuàng)建,而且不會被銷毀或者替換。其他所有可見的視圖都是它的子視圖。

如果你的app可以在外部屏幕展示視圖,你將需要創(chuàng)建一個額外的UIWindow,但是在本章節(jié),我們假設(shè)只有一個屏幕,只有一個window

初始化一個window必須充滿設(shè)備的屏幕,確保設(shè)置window的frame等于屏幕的bounds,如果你使用的是main.storyboard,在app加載的時候會自動幫你創(chuàng)建,AppDelegate頂部的那個注解@UIApplicationMain 。 如果你需要自己創(chuàng)建可以通過以下方式:

// 創(chuàng)建window iOS 9 之前
let w = UIWindow(frame: UIScreen.mainScreen().bounds)
// iOS 9 之后
let w = UIWindow()

window必須在app的整個生命周期都被持有。所以AppDelegate擁有window的強(qiáng)引用,我們一般不會將一個view直接放在主window上,而是通過將一個ViewController付給window的rootViewController屬性,
如果你使用的是main.stroyboard 這個事情也是系統(tǒng)做好的,rootViewController 會直接指向main.stroyboard的initial view controller 。 一個VC成為window的rootViewController 后,它的view也會變成UIWindow的直接子視圖。你的app將會在調(diào)用window?.makeKeyAndVisible()時顯示。

總結(jié)下初始化、創(chuàng)建、配置顯示主窗口的過程:(應(yīng)該考慮兩種情況)

  • 通過main storyboard 創(chuàng)建

    • storyboard 文件在 Info.plist 的鍵為 Main storyboard file base name 中指定(UIMainStoryboardFile)
    • UIapplicationMain 實例化 UIWindow 并設(shè)置好 frame
    • 把設(shè)置好的 UIWindow 的實例指定給 app delegate 的 window 屬性
    • 實例化 view controller 并指定給 window 的 rootViewController 屬性
    • 這些都發(fā)生在 app delegate 的 application:didFinishLaunchingWithOptions: 被調(diào)用之前
  • 不使用 main storyboard

    • 因為項目模板都會自帶 storyboard,所以需要做以下步驟來獲得一個純凈的空白項目
      • 在 General pane,選擇 Main 并且刪除
      • 刪除 Main.storyboard 以及 ViewController.swift
      • 刪掉 AppDelegate.swift 中的所有內(nèi)容
      • 然后替換為以下代碼
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        self.window = UIWindow()
        self.window!.rootViewController = UIViewController()  // 也可以是自定義的子類
        self.window!.backgroundColor = UIColor.whiteColor()
        self.window!.makeKeyAndVisible()
        return true
    }
}

上面是app運(yùn)行最少需要的代碼,運(yùn)行指揮得到一個白板。

如何使用window的子類:

  • main storyboard

    在app運(yùn)行時,在UIApplicationMain初始化完appDelegate后,就會詢問appDelegate的window屬性是否有值,如果為nil,UIApplicationMain就會創(chuàng)建一個UIWindow的實例,如果不為nil,直接使用其值作為main window。
    ??:

lazy var window : UIWindow? = {
    return MyWindow()
}()
  • 不使用main storyboard

    這個就簡單了,window本來就是自己初始化的,直接 window = MyWondow()

app一運(yùn)行,會有很多方法來引用主window

  • 如果一個 UIView 在界面中,它自動會有一個 window 屬性,里面有對 window 的引用

    • 也可以使用 UIView 的 window 屬性來檢查這個 view 是不是被嵌入到了 window 中。如果不是,那么 window 屬性為 nil。一個 window 屬性為 nil 的 UIView 對用戶來說是不可見的.(self.view.window)
  • app delegate 實例會維護(hù)一個指向 window 的引用(window 屬性),可以通過 shared application 來獲取

    • let w = UIApplication.sharedApplication().delegate!.window
    • 如果想要不那么通用的方法,可以顯式轉(zhuǎn)換成 app delegate 類
    • let w = (UIApplication.sharedApplication().delegate as! AppDelegate).window
  • shared application 會在 keyWindow 屬性中維護(hù)一個指向 window 的引用

    • let w = UIApplication.sharedApplication().keyWindow
    • 這個引用不是很穩(wěn)定,因為系統(tǒng)可能會創(chuàng)建臨時的 window 并且把它們當(dāng)做 key window,所以最好還是用第二種

Single View Application 模板

本章節(jié)重點(diǎn)是view,所以暫且不介紹ViewController相關(guān)內(nèi)容,后面章節(jié)會介紹

新建的Single View Application會自動創(chuàng)建一個Main.storyboard 和 ViewController.swift作為window的rootViewController。

現(xiàn)在我們可以在ViewController.swift中通過代碼添加一個view上去
就在viewDidLoad方法中

 override func viewDidLoad() {
        super.viewDidLoad()

        let mainview = self.view
        let v = UIView(frame:CGRectMake(100,100,50,50))
        v.backgroundColor = UIColor.redColor() // 紅色小塊
        mainview.addSubview(v) // 添加到mainview
        
    }

不包含main storyboard的實現(xiàn)方式

func application(application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
        -> Bool {
            self.window = UIWindow()
            self.window!.rootViewController = UIViewController()
            // here we can add subviews
            let mainview = self.window!.rootViewController!.view
            let v = UIView(frame:CGRectMake(100,100,50,50))
            v.backgroundColor = UIColor.redColor() // small red square
            mainview.addSubview(v) // add it to main view
            // and the rest is as before...
            self.window!.backgroundColor = UIColor.whiteColor()
            self.window!.makeKeyAndVisible()
            return true
    }

Subview and Superview (子視圖和父視圖)



在iOS中,一個subview的一部分或者全部都可以出現(xiàn)在其superview的外部。一個 view 可以和另一個 view 重疊,即使不是其 subview 也可以繪制部分或全部繪制在另一個 view 之前。

測試了下,在interface Builder中拖拽的時候是被擋住的

配圖

但是運(yùn)行結(jié)果是沒有擋住的

配圖

View 層級 的特點(diǎn)

  • 如果一個 view 被移出或者引入它的 superview,它的 subview 會跟著
  • 一個 view 的透明度會被其 subview 繼承
  • 一個 view 可以限制 subview 的顯示范圍,比如不讓 subview 超出 view 本身的范圍,這叫做 clipping,被設(shè)置在 clipsToBounds
    屬性中
  • 一個 superview 擁有它的 subview
  • 如果一個 view 的尺寸變化了,它的 subview 也會自動被重新設(shè)置尺寸

一個UIView有一個superview屬性和一個subviews(數(shù)組)屬性(都是可空類型)??梢該?jù)此來判斷視圖層級。另外也有一個 isDescendantOfView: 方法來檢查一個 view 是不是另一個 view 的 subview (可以不是直接子視圖)。View 還有一個 tag 屬性,可以通過 viewWithTag: 來進(jìn)行引用。我們最好給所有subviews設(shè)置不同的tag


使用代碼操作視圖層級:(可以直接操作,也可以配合動畫)

  • addSubview: 方法添加一個 subview
  • removeFromSuperview 移除一個 subview
  • insertSubview:atIndex: 指定index層級
  • insertSubview:belowSubview: 在某個view下面添加一個subview
  • insertSubview:aboveSubview: 在某個view上面添加一個subview
  • exchangeSubviewAtIndex:withSubviewAtIndex: 交換兩個subview的位置
  • bringSubviewToFront: 將某個subview移動到最前面
  • sendSubviewToBack: 將某個subview放到最后面

沒有一個方法可以直接移除一個 view 的所有 subview。然而,因為一個 view 的 subview 數(shù)組是一個不可變的數(shù)組,所以可以用如下方法一次移除全部:

myView.subviews.forEach {$0.removeFromSuperview}


重寫下列方法就可以根據(jù)需要在不同的情況下進(jìn)行不同的操作:

  • didAddSubview:, willRemoveSubview:
  • didMoveToSuperview, willMoveToSuperview:
  • didMoveToWindow, willMoveToWindow:
Visibility and Opacity(可見性和透明度)

視圖的可見性可以通過設(shè)置 hidden 屬性來更改。一個隱藏的 view 無法接收觸摸事件,所以對于用戶來說相當(dāng)于不存在,但實際上是存在的,所以仍然可以在代碼中對其操作

View 的背景顏色可以通過其 backgroundColor 屬性來設(shè)置,顏色屬于 UIColor 類。如果 backgroundColor 為 nil(默認(rèn)值) 那么背景就是透明的。

可以通過設(shè)置 view 的 alpha 屬性來修改透明程度,1.0 是完全不透明,0.0 是透明。假設(shè)一個 view 的 alpha 是 0.5,那么它的 subview 的 alpha 都是以 0.5 為基準(zhǔn)的,不可能高于 0.5。而 UIColor 也有 alpha 這個屬性,所以即使一個 view 的 alpha 是 1.0,它仍舊可能是透明的,因為其 backgroundColor 可以是透明的。一個 alpha 為 0.0 的 view 是完全透明的所以是不可見的,通常來說也不可能被點(diǎn)擊。

View 的 alpha 屬性不僅影響背景顏色,也會影響其內(nèi)容的透明度。(比如一個背景色將會滲透圖片)

我大概實驗了下,應(yīng)該是下面這個?? 的意思:

配圖

view的opaque(不透明度),并不會影響view的樣子,更多的是對于系統(tǒng)繪制時的提示。如果一個 view 的 opaque 設(shè)為 true,因為不用考慮透明的繪制,所以效率會高一點(diǎn),并且再設(shè)置透明的背景顏色或者 alpha 屬性都無效。可能會讓人吃驚,它的默認(rèn)值是 true

但是我設(shè)置了view的alpha=0.3 還是有不透明的效果(或者是疊加),在設(shè)置前后都打印了opaque的值都是true

配圖

配圖

然后還手動把opaque設(shè)置為false,但是也沒有什么用,懂的解釋下??


Frame

View 的 frame屬性(CGRect類) 是它本身的長方形在 superview 中的位置,注意是在 superview 的坐標(biāo)系中的位置。默認(rèn)來說,superview 的坐標(biāo)系原點(diǎn)在左上,向右 x 增加,向下 y 增加。

看一個frame使用的簡單的例子:

let mainview = self.view
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:CGRectMake(41, 56, 132, 194))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView(frame:CGRectMake(43, 197, 160, 230))
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
mainview.addSubview(v3)

以上很基礎(chǔ)的代碼,不贅述了。


Bounds and Center

bounds 屬性對應(yīng)的是一個 view 在自己的坐標(biāo)系統(tǒng)中的矩形尺寸(注意,frame 是在 superview 的坐標(biāo)系下的)

let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)

這是一種很常見的 bounds 的用法,當(dāng)你需要往一個 view 里放東西的時候,無論是手動繪制還是放置一個 subview,通常都要使用 view 的 bounds

配圖

當(dāng)你改變一個 view 的 bounds 時,它的 frame 也會對應(yīng)改變,frame 的改變是基于其中心點(diǎn)的(中心點(diǎn)不會變),下面的代碼描述了這個情況:

let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v2.bounds.size.height += 20
v2.bounds.size.width += 20\

效果就是從上圖變成了下圖,增加的 20 會被均勻分布在上下左右,正好抵消了之前的設(shè)置。

配圖

當(dāng)創(chuàng)建一個 UIView 時,其 bounds 的坐標(biāo)原點(diǎn)是 (0.0, 0.0),也就是左上角,如果改變了 bounds 的原點(diǎn),也就改變了其坐標(biāo)系,其 subview 一般也會有變化,下面代碼描述了這種情況

let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.bounds.origin.x += 10
v1.bounds.origin.y += 10
配圖

如果你把那兩個10改成20 效果如圖

配圖

我們并沒有設(shè)置subview的任何屬性,然而subview移動了,可以看到 subviw 向著原點(diǎn)移動方向的反方向進(jìn)行了移動,這是因為一個 view 的原點(diǎn)與其 frame 的左上角一致

我們看到改變 boundssize 會影響其 framesize ,改變 framesize 會影響其 boundssize 。只有 view 的 center 不會影響 boundssize 。 這個屬性代表了 subviewsuperview 中的 position 。

書中給出獲取一個view的center的方法

let c = CGPointMake(theView.bounds.midX,theView.bounds.midY)

但是經(jīng)過我驗證,這個方法獲得的是view相對于自己坐標(biāo)的,我自己測試代碼如下 , 或許應(yīng)該加上x , y 坐標(biāo)

let v3 = UIView(frame:CGRectMake(113, 111, 132, 194))
print(v3.center)    // (179.0, 208.0)
let c = CGPointMake(v3.bounds.midX, v3.bounds.midY)
print(c)    // (66.0, 97.0)
let c1 = CGPoint(x: v3.bounds.midX+113, y: v3.bounds.midY+111)
print(c1)   // (179.0, 208.0)

view的bounds和center互不影響,相互獨(dú)立的。frame是center和bounds便捷的表達(dá)。大多數(shù)情況我們只需要使用frame就可以了。一般會通過init(frame:) 來創(chuàng)建一個view。注意有些情況下 frame 會沒有什么意義,但是 bounds 和 center 總是有效的,所以建議多用 bounds 和 center 的組合,也比較容易理解。

  • bounds: 一個 view 自己的坐標(biāo)系統(tǒng)
  • center: 一個 view 的坐標(biāo)系統(tǒng)和其 superview 的坐標(biāo)系統(tǒng)的關(guān)系

可以用如下方法來進(jìn)行不同 view 之間的坐標(biāo)轉(zhuǎn)換

  • convertPoint:fromView:, convertPoint:toView:
  • convertRect:fromView:, convertRect:toView:

如果第二個參數(shù)是nil,系統(tǒng)自動填補(bǔ)為window

比如我們上面算center的例子也可以這樣轉(zhuǎn)換下

print(v3.center)    // (179.0, 208.0)
let c = CGPointMake(v3.bounds.midX, v3.bounds.midY)
let c2 = v3.convertPoint(c, toView: self.view)
print(c)    // (66.0, 97.0)
print(c2)   // (179.0, 208.0)

注意,通過改變 center 來設(shè)置 view 的位置時,如果高或?qū)挷皇桥紨?shù),那么可能會導(dǎo)致 misaligned(錯位)??梢酝ㄟ^打開模擬器的 Debug -> Color Misaligned Images 來進(jìn)行檢測。一個簡單的方法是調(diào)整好位置之后調(diào)用 makeIntegralInPlace 來設(shè)置 view 的 frame


Window Coordinates and Screen Coordinates(窗口坐標(biāo)和屏幕坐標(biāo))

設(shè)備屏幕是沒有 frame 的,但是有 bounds。Main window 也沒有 superview,不過其 frame 被設(shè)置為屏幕的 bounds,如:

let w = UIWindow(frame: UIScreen.mainScreen().bounds)
//iOS 9
let w = UIWindow() //系統(tǒng)自動設(shè)置為上面代碼

大多數(shù)情況下,window是充滿整個屏幕的,所以大多數(shù)情況下window的坐標(biāo)和screen的坐標(biāo)是一樣的。

現(xiàn)在的 iOS 中坐標(biāo)系和手機(jī)是否選擇是有關(guān)的,有如下兩個屬性:(在實際開發(fā)中基本不會碰到

  • UIScreen 的 coordinateSpace 屬性
    • 這個坐標(biāo)空間會旋轉(zhuǎn),就是高和寬在設(shè)備旋轉(zhuǎn)時會呼喚,(0.0, 0.0) 是這個 app 本身的左上方
  • UIScreen 的 fixedCoordinateSpace 屬性
    • 這個坐標(biāo)空間不會變化,就是物理上的左上角,從用戶來看,這里的 (0.0, 0.0) 可能是 app 本身的任何一個角

可以用下面的方法來對不同坐標(biāo)空間進(jìn)行轉(zhuǎn)換:

  • convertPoint:fromCoordinateSpace:,convertPoint:toCoordinateSpace:
  • convertRect:fromCoordinateSpace:, convertRect:toCoordinateSpace:

假設(shè)界面中有一個 UIView v,我們想知道它的實際設(shè)備坐標(biāo),可以用下面的代碼:

let r = v.superview!convertRect(v.frame, toCoordinateSpace: UIScreen.mainScreen().fixedCoordinateSpace)

但實際上你需要這種信息的機(jī)會非常少(反正我是沒遇到過需要使用的),或者其實幾乎都不用擔(dān)心 window 坐標(biāo),因為所有的可見操作都會在 root view contoller 的 main view 中進(jìn)行,它的 bounds 是會自動調(diào)整的。


Transform ( 變換 )

一個 view 的 transform 屬性改變這個 view 是如何被繪制的,實際上就是一個 CGAffineTransform類的 3x3 矩陣(線性代數(shù)中的概念)。所有的變換都是以這個 view 的 center 做基準(zhǔn)的。

??

let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
旋轉(zhuǎn)

上面代碼的例子只是對前面例子的v1做了45度的旋轉(zhuǎn)。

我們?nèi)绻蛴∠聉1旋轉(zhuǎn)前后的三個屬性:

print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
//        frame : (113.0, 111.0, 132.0, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
 v1.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
//        frame : (63.7415946665928, 92.7415946665928, 230.516810666815, 230.516810666815) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)

發(fā)現(xiàn)只有frame變了,center和bounds都沒有變 ,但是 frame 的數(shù)值已經(jīng)沒有意義,因為現(xiàn)在它的尺寸是能夠覆蓋當(dāng)前 view 的最小的矩形,并不會隨著 view 的旋轉(zhuǎn)而選擇。

如果我們把旋轉(zhuǎn)換成縮放

       print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
       v1.transform = CGAffineTransformMakeScale(1.8, 1)
       print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
//        frame : (113.0, 111.0, 132.0, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
//        frame : (60.2, 111.0, 237.6, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)

還是只有frame發(fā)生了改變 。因為他的位置并沒有變只是被拉長了。bouds并不會因此而改變

變換矩陣的計算可以連接的,所以不同的變換是可以疊加的,并且順序是重要的(矩陣乘法不滿足交換律)

??

let v1 = UIView(frame:CGRectMake(20, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds)
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
        
v2.transform = CGAffineTransformMakeTranslation(100, 0)
v2.transform = CGAffineTransformRotate(v2.transform, 45 * CGFloat(M_PI)/180.0)

這個例子我們先在主view上放了兩個完全重疊的view,然后對v2做了平移和旋轉(zhuǎn)的變換 ,兩個疊加起來的

效果:

先平移后旋轉(zhuǎn)
v2.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
v2.transform = CGAffineTransformTranslate(v2.transform, 100, 0)
先旋轉(zhuǎn)后平移

也可以使用這個方法CGAffineTransformConcat:

 let r = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
 let t = CGAffineTransformMakeTranslation(100, 0)
 v2.transform = CGAffineTransformConcat(t,r)

這個也需要注意順序


Trait Collections and Size Classes

界面上的每個 view(或者ViewController) 都有一個 traitCollection 屬性 , 值是一個 UITraitCollection,包含下面四個屬性:

  • displayScale 由當(dāng)前屏幕決定的縮放尺寸,1 4以前的機(jī)型 基本沒有了應(yīng)該 2 、(4,5,6 3) 3、 (iPhone 6 plus/6s Plus) 。(和UIScreen的scale值是一樣的)
  • userInterfaceIdiom 一個 UserIterfaceIdiom 值,可能是 .Phone 或 .Pad,來標(biāo)志不同的設(shè)備,默認(rèn)來說和 UIDevice 的 userInterfaceIdiom 屬性一致
  • horizontalSizeClass, verticalSizeClass,是 UIUserInterfaceSizeClass 值,可能是 .Regular.Compact
    • 水平和豎直都是 .Regular -> iPad
    • 水平是 .Compact 豎直是 .Regular -> iPhone 在垂直方向,或者 iPad 的分屏應(yīng)用
    • 水平和豎直都是 .Compact -> iPhone 在水平方向(iPhone 6/6s plus除外)
    • 水平是 .Regular 豎直是 .Compact -> iPhone 6/6s Plus 在水平方向

當(dāng)應(yīng)用運(yùn)行時如果 trait collection 發(fā)生改變,會調(diào)用traitCollectionDidChange 方法

traitCollectionDidChange傳入的參數(shù)是舊的traitCollection值:

override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
    print("old----\(previousTraitCollection?.verticalSizeClass.rawValue)")
    print("old----\(previousTraitCollection?.horizontalSizeClass.rawValue)")
    print("new----\(self.view.traitCollection.verticalSizeClass.rawValue)")
    print("new----\(self.view.traitCollection.horizontalSizeClass.rawValue)")
}

切換幾次橫豎屏的結(jié)果:

配圖

traitCollection還可以自己設(shè)定,這個特性將在后面章節(jié)講到


Layout

superview 移動的時候 subview 就會移動。subview 大小和位置 會隨著 superview改變,這就是layout。

一些superview動態(tài)改變的例子:

  • 屏幕旋轉(zhuǎn)的時候,左上角會發(fā)生變化,長寬也要對調(diào)
  • 我們的app需要等比例匹配不同的設(shè)備尺寸
  • universal app需要運(yùn)行在iPad和iPhone上,所以自己需要知道自己運(yùn)行的環(huán)境來適應(yīng)不同的屏幕
  • 從xib初始化的view,需要resize去適應(yīng)所在的view
  • view需要適應(yīng)別的view的變化對自己的影響,比如navagationBar隱藏和顯示
  • 。。。

在以上的任何情況下其他view可能需要Layout


Layout 有三種主要的執(zhí)行方式

  • 手動 layout:superview 在被更改尺寸會會發(fā)送 layoutSubviews 消息,如果你新建自己的子類并且重寫 layoutSubviews 就可以手動進(jìn)行更改,這很麻煩,但是可以做任何你想做的事情
  • Autoresizing: iOS 6 之前的方式,主要是通過自己的 autoresizingMask 屬性來變化
  • Autolayout:根據(jù) view 的 constraints(NSLayoutConstraint) 來進(jìn)行變化,是很強(qiáng)大的功能,不用寫代碼就可以進(jìn)行復(fù)雜的定制

Autoresizing(自動調(diào)整大?。?/h5>

Autoresizing 是一種自動拉伸和固定大小的一種概念,view有一個autoresizingMask 屬性 ,這個屬性是一個UIViewAutoresizing 的值 。默認(rèn)是.None 。下面舉例來看看它的用法:

let mainview = self.view
let v1 = UIView(frame:CGRectMake(100, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:CGRectMake(0, 0, 132, 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView(frame:CGRectMake(v1.bounds.width-20, v1.bounds.height-20, 20, 20))
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.addSubview(v3)

v2 和 v1 寬度相等

執(zhí)行結(jié)果:

配圖

如果我加上一句代碼,改變了v1的寬度呢?

v1.bounds.size.width += 40

結(jié)果如圖:

就變成這樣了

這時候就可以利用autoresizingMask屬性來指定v2的寬度可伸縮。

v2.autoresizingMask = .FlexibleWidth

結(jié)果:

配圖

這里注定一點(diǎn),要先指定可伸縮,再改變大小

同理v3也可以:

v2.autoresizingMask = .FlexibleWidth
v3.autoresizingMask = [.FlexibleTopMargin, .FlexibleLeftMargin]

v1.bounds.size.width += 40
v1.bounds.size.height -= 50

v3就相當(dāng)于在左邊和上邊放了彈簧

配圖

其實這種類似于約束布局,現(xiàn)在大部分會選擇使用Autolayout,下面小結(jié)看下Autolayout的用法。
Autolayout部分請戳:Views - AutoLayout

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容