玩轉swift — UIKit 之 UIView(1)

王隆帥發表於2019-03-01

概述

UIView類通過定義一個在螢幕和介面上的矩形區域來管理這塊區域的內容。在執行時,檢視物件處理其區域內的任何內容渲染,還處理與該內容的任何相互作用。

進入正題

一、初始化檢視物件

// 純程式碼初始化執行
public init(frame: CGRect)

// 使用 Interface Builder 構造介面執行這個
public init?(coder aDecoder: NSCoder)複製程式碼

現在開始測試:

先構建一個自定義View: TestInitView, 並新增兩個初始化函式,如下:

// 純程式碼走這個
override init(frame: CGRect) {

    super.init(frame: frame);

    print("執行了 init(frame: CGRect)")
}

// 使用 Interface Builder 構造介面走這個
required init?(coder aDecoder: NSCoder) {

    super.init(coder: aDecoder);
    print("執行了 init?(coder aDecoder: NSCoder)")
}複製程式碼

1、純程式碼構造介面

測試程式碼如下:

// test view 純程式碼初始化
func testViewInit() {

    let testView = TestInitView.init(frame: CGRect.init(x: 100, y: 100, width: 100, height: 100));
    testView.backgroundColor = #colorLiteral(red: 0.1215686277, green: 0.01176470611, blue: 0.4235294163, alpha: 1);

    self.view.addSubview(testView);
}複製程式碼

測試結果:

執行了 init(frame: CGRect)複製程式碼

注意:此方法是由自己呼叫,來初始化物件的。

2、使用 Interface Builder 構造介面

① 使用storyBoard來構建介面,並拖入一個View,並將此View的Class設定為 TestInitView,如下:

② 執行程式,結果如下:

執行了 init?(coder aDecoder: NSCoder)複製程式碼

注意:由系統來呼叫,自己不能呼叫。

二、配置檢視的視覺外觀

// 檢視的背景顏色
@NSCopying open var backgroundColor: UIColor?
// 檢視的alpha值
open var alpha: CGFloat
// 確定檢視是否不透明的布林值(它卻決定不了當前UIView是不是不透明),用處在於給繪圖系統提供一個效能優化開關。
open var isOpaque: Bool
// 確定檢視是否被隱藏的布林值
open var isHidden: Bool
// 這個屬性定義了一個非預設的著色顏色值,其值的設定會影響到以檢視為根檢視的整個檢視層次結構。
@available(iOS 7.0, *)
open var tintColor: UIColor!
// 本檢視及父檢視的tintColor或tintAdjustmentMode屬性改變時自動呼叫
@available(iOS 7.0, *)
open func tintColorDidChange()
// 一個列舉值,定義了tint color的調整模式。
@available(iOS 7.0, *)
open var tintAdjustmentMode: UIViewTintAdjustmentMode
// 決定子檢視是否被限定在當前檢視的bounds中
open var clipsToBounds: Bool
// 決定在檢視重畫之前是否先清理檢視以前的內容
open var clearsContextBeforeDrawing: Bool
// 一個通過alpha通道來掩蓋一個view的內容的可選view。
@available(iOS 8.0, *)
open var mask: UIView?
// 為此類的例項建立圖層的類
open class var layerClass: Swift.AnyClass { get }
// UIView的檢視層
open var layer: CALayer { get }複製程式碼

1、alpha && isOpaque && isHidden

測試程式碼如下:

func testalpha_isOpaque_isHidden() {

    // test alpha
    let leftAlphaView = self.getNewTestView(index_i: 0, index_j: 0);
    leftAlphaView.alpha = 0.0;

    let centerAlphaView = self.getNewTestView(index_i: 1, index_j: 0);
    centerAlphaView.alpha = 0.5;

    let rightAlphaView = self.getNewTestView(index_i: 2, index_j: 0);
    rightAlphaView.alpha = 1.0;

    // test isHidden

    let leftIsHiddenView = self.getNewTestView(index_i: 0, index_j: 1);
    leftIsHiddenView.isHidden = true;

    let rightIsHiddenView = self.getNewTestView(index_i: 1, index_j: 1);
    rightIsHiddenView.isHidden = false;

    // test isOpaque

    let leftDownOpaqueView = self.getNewTestView(index_i: 0, index_j: 2);
    let leftUpOpaqueView = self.getNewTestView(index_i: 0, index_j: 3);
    leftUpOpaqueView.alpha = 1.0;
    leftUpOpaqueView.isOpaque = true;
    leftUpOpaqueView.backgroundColor = #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1);
    leftUpOpaqueView.center = CGPoint.init(x: leftDownOpaqueView.center.x, y: leftDownOpaqueView.center.y + 25);

    let rightDownOpaqueView = self.getNewTestView(index_i: 1, index_j: 2);
    let rightUpOpaqueView = self.getNewTestView(index_i: 1, index_j: 3);
    rightUpOpaqueView.alpha = 0.5;
    rightUpOpaqueView.isOpaque = false;
    rightUpOpaqueView.backgroundColor = #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
    rightUpOpaqueView.center = CGPoint.init(x: rightDownOpaqueView.center.x, y: rightDownOpaqueView.center.y + 25);
    }

func getNewTestView(index_i: NSInteger, index_j: NSInteger) -> UIView {

    let testView = UIView.init(frame: CGRect.init(x: 30 + index_i * 80, y: 80 + index_j * 80, width: 50, height: 50));
    testView.backgroundColor = #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1);

    self.view.addSubview(testView);

    return testView;
}複製程式碼

測試結果如下:

注意:當把UIView的alpha屬性設成0,或者把isHidden設成true的時候,當前UIView和它所包含的子UIView都會變成不可見,同時也不會再響應event事件。isOpaque,而不是決定View的是否是不透明。即View不透明時,isOpaque需要設定為true,來優化效能,有透明度時,isOpaque需要設定為false,防止不可預測事情發生(我也不知道啥事情,測試顯示沒啥區別!)。

2、tintColor && tintAdjustmentMode && tintColorDidChange

① 首先測試一下,tintColor的設定:

自定義 TIntColorTestView

// test tintColor
func testTintColor() {

    self.label.frame = CGRect.init(x: 30, y: 80, width: 250, height: 50);
    self.label.text = "這是王隆帥的label";
    self.label.textColor = self.tintColor;
    self.addSubview(self.label);

    let button = UIButton.init(type: .system);
    button.frame = CGRect.init(x: 30, y: 140, width: 250, height: 50);
    button.addTarget(self, action: #selector(btnClick), for: .touchUpInside)
    button.setTitle("這是王隆帥的btn", for: .normal);

    self.addSubview(button);

    var image = UIImage.init(named: "imageToColor");
    image = image?.withRenderingMode(.alwaysTemplate);
    let imageView = UIImageView.init(frame: CGRect.init(x: 30, y: 200, width: 250, height: 50));
    imageView.image = image;
    self.addSubview(imageView);

}

func btnClick() {

    self.tintColor = UIColor.init(red: CGFloat(Double(arc4random() % 255) / 255.0), green: CGFloat(Double(arc4random() % 255) / 255.0), blue: CGFloat(Double(arc4random() % 255) / 255.0), alpha: 1.0);
}

override func tintColorDidChange() {

    self.label.textColor = self.tintColor;
}複製程式碼

初始化這個View:

func testTintColor() {

    let tintColorView = TIntColorTestView.init(frame: self.view.bounds);
    self.view.addSubview(tintColorView);
}複製程式碼

執行程式,並不斷點選btn,結果如下:

由上可知,tintColor會影響到以檢視為根檢視的整個檢視層次結構。主要是改變系統的某些控制元件,比如 UIButton, UISlider, UIProgressView, UIStepper, UIImageView等等。假如想要更改label的文字顏色,或者某些View的背景顏色等,可以監聽 tintColorDidChange,來做相應更改!

② 再來測試一下 tintAdjustmentMode

@available(iOS 7.0, *)
public enum UIViewTintAdjustmentMode : Int {

    // 檢視的著色調整模式與父檢視一致
    case automatic
    // 檢視的tintColor屬性返回UIExtendedSRGBColorSpace 顏色空間的顏色
    case normal
    // 檢視的tintColor屬性返回 UIExtendedGrayColorSpace 顏色空間的顏色
    case dimmed
}複製程式碼

測試程式碼,在上面的基礎上 加上以下程式碼:

print("normal ----- (self.tintAdjustmentMode.rawValue)");
print("normal ----- (self.tintColor)");

self.tintAdjustmentMode = .dimmed;

print("dimmd ----- (self.tintAdjustmentMode.rawValue)");
        print("dimmd ----- (self.tintColor)");

button.tintAdjustmentMode = .normal;
imageView.tintAdjustmentMode = .automatic;複製程式碼

測試結果如下:

normal ----- 1
normal ----- Optional(UIExtendedSRGBColorSpace 0 0.478431 1 1)
dimmd ----- 2
dimmd ----- Optional(UIExtendedGrayColorSpace 0.484669 0.8)複製程式碼

由圖及列印可知,normaldimmd,確實對應著 UIExtendedSRGBColorSpaceUIExtendedGrayColorSpace 兩個顏色空間!並且,imageView設定的 .automatic 是繼承了父View的 .dimmed 屬性。

想要了解更多關於顏色空間,可以看這篇文章

3、clipsToBounds

測試程式碼如下:

// 測試 clipsToBounds
func testClipsToBounds() {

    // clipsToBounds false
    let clipsView1_down = self.getNewTestView(index_i: 0, index_j: 0);
    clipsView1_down.clipsToBounds = false;

    let clipsView1_up = self.getNewTestView(index_i: 0, index_j: 1);
    clipsView1_up.backgroundColor = #colorLiteral(red: 0.1294117719, green: 0.2156862766, blue: 0.06666667014, alpha: 1);
    clipsView1_up.center = CGPoint.init(x: 25, y: 50);

    clipsView1_down.addSubview(clipsView1_up);

    // clipsToBounds true
    let clipsView2_down = self.getNewTestView(index_i: 1, index_j: 0);
    clipsView2_down.clipsToBounds = true;

    let clipsView2_up = self.getNewTestView(index_i: 1, index_j: 1);
    clipsView2_up.backgroundColor = #colorLiteral(red: 0.1294117719, green: 0.2156862766, blue: 0.06666667014, alpha: 1);
    clipsView2_up.center = CGPoint.init(x: 25, y: 50);

    clipsView2_down.addSubview(clipsView2_up);
}複製程式碼

測試結果:

4、clearsContextBeforeDrawing

見名知意,此屬性決定繪製前是否清屏,預設為true。當這個屬性被設定為時true,UIKIt會在呼叫 drawRect: 方法之前,把即將被該方法更新的區域填充為透明的黑色。將這個屬性設定為false可以取消相應的填充操作,view中原有內容會保留。

如果將此屬性的值設定為false,則我們應該確保在 draw(:) 方法中正確繪製檢視的內容。在此前提下,可以提高效能。

5、mask

測試程式碼如下:

// 測試 mask
func testMask() {

    let view1 = UIView.init(frame: CGRect.init(x: 20, y: 80, width: 80, height: 80));
    view1.backgroundColor = UIColor.blue;

    let maskView1 = UIView.init(frame: view1.bounds);
    maskView1.backgroundColor = UIColor.red;
    maskView1.alpha = 0.1;

    view1.mask = maskView1;
    self.view.addSubview(view1);


    let view2 = UIView.init(frame: CGRect.init(x: 120, y: 80, width: 80, height: 80));
    view2.backgroundColor = UIColor.blue;

    let maskView2 = UIView.init(frame: view2.bounds);
    maskView2.backgroundColor = UIColor.green;
    maskView2.alpha = 0.5;

    view2.mask = maskView2;
    self.view.addSubview(view2);


    let view3 = UIView.init(frame: CGRect.init(x: 220, y: 80, width: 80, height: 80));
    view3.backgroundColor = UIColor.blue;

    let maskView3 = UIView.init(frame: view3.bounds);
    maskView3.backgroundColor = UIColor.brown;
    maskView3.alpha = 1.0;

    view3.mask = maskView3;
    self.view.addSubview(view3);
}複製程式碼

測試結果如下:

由上圖可知,mask自帶的顏色不會顯示,最終效果圖怎麼顯示只跟mask每個point的alpha相關。(本例是全部都是一樣的alpha,假如想要部分為透明,可以新增含有alpha通道的圖片)

可以這樣理解,是將mask的每個point的alpha賦值給View的重疊部分相對應的point,這樣view的重疊每個point都有個alpha值了,view重疊部分就可能顯示多種透明色。

5、layer && layerClass

測試程式碼:

// 測試 layer
func testLayer() {

    let layer = CALayer.init();
    layer.bounds = CGRect.init(x: 0, y: 0, width: 100, height: 100);
    layer.position = CGPoint.init(x: 100, y: 200);
    layer.contents = UIImage.init(named: "imageToColor")?.cgImage;
    layer.cornerRadius = 10.0;
    layer.masksToBounds = true;

    self.view.layer.addSublayer(layer);
}複製程式碼

測試結果:

總結:UIView和CALayer是相互依賴的關係。UIView依賴於CALayer提供的內容,CALayer依賴UIView提供的容器來顯示繪製的內容。歸根到底CALayer是這一切的基礎,如果沒有CALayer,UIView自身也不會存在,UIView是一個特殊的CALayer實現,新增了響應事件的能力。

想要更詳細瞭解兩者詳細可以檢視這兩篇文章(第一篇第二篇),這裡就不再贅述了!

三、配置事件相關行為

// 設定檢視的可互動性
open var isUserInteractionEnabled: Bool
// 設定是否支援多點觸控
open var isMultipleTouchEnabled: Bool
// 設定控制元件接受事件時的排他性
open var isExclusiveTouch: Bool複製程式碼

1、isUserInteractionEnabled

當一個檢視物件的 isUserInteractionEnabled 被置為 false ,則這個檢視物件就被從響應者鏈裡移除,它所負責響應的事件全部無效。它的 subviews 事件也會被丟棄。當重新設為 true 時,則事件可以正常的傳遞給該檢視物件。額外的,UIImageViewUILabel 預設的 isUserInteractionEnabledfalseUIViewisUserInteractionEnabled 預設是 true

2、isMultipleTouchEnabled

測試程式碼如下:

// 測試 isMultipleTouchEnabled
var touchNums = 0;
func testIsMultipleTouchEnabled() {

    self.view.isMultipleTouchEnabled = true;
}

override func touchesBegan(_ touches: Set, with event: UIEvent?) {

    touchNums = touchNums + touches.count;
    print(touchNums);
}

override func touchesEnded(_ touches: Set, with event: UIEvent?) {

    touchNums = touchNums - touches.count;
    print(touchNums);
}

override func touchesCancelled(_ touches: Set, with event: UIEvent?) {

    touchNums = touchNums - touches.count;
    print(touchNums);
}複製程式碼

分別用十個指頭點選螢幕,測試結果如下:

2
4
5
2
0複製程式碼

由結果可知,螢幕最多支援五點觸控。

isMultipleTouchEnabled 設定為 false,可得以下結果:

1
0
1
0複製程式碼

即,最多支援一個觸控事件。

3、isExclusiveTouch

測試程式碼如下:

// 測試 isExclusiveTouch
func testIsExclusiveTouch() {

    let btn1 = UIButton.init(frame: CGRect.init(x: 100, y: 100, width: 100, height: 100))
    btn1.backgroundColor = UIColor.red;
//        btn1.isExclusiveTouch = true;
    btn1.addTarget(self, action: #selector(touchClickBtn), for: .touchUpInside);
    self.view.addSubview(btn1);

    let btn2 = UIButton.init(frame: CGRect.init(x: 220, y: 100, width: 100, height: 100))
    btn2.backgroundColor = UIColor.blue;
//        btn2.isExclusiveTouch = true;
    btn2.addTarget(self, action: #selector(touchClickBtn), for: .touchUpInside);
    self.view.addSubview(btn2);
}

func touchClickBtn() {

    print("btn 被點選了!!");
}複製程式碼

執行程式,手指先點選紅色按鈕,摁住不放,另一個手指點選藍色按鈕,然後同時放開,得出下面結果:

btn 被點選了!!
btn 被點選了!!複製程式碼

由此可知,兩個按鈕同時響應了點選事件。

將程式碼中註釋的程式碼開啟,重新執行程式,並執行上面的操作,得出下面的結果:

btn 被點選了!!複製程式碼

即,設定了View接收事件的排他性為true,則同一時間在兩個同級的View之間,只能有一個事件觸發!

四、設定位置及大小

// 設定檢視在其父檢視中的位置及大小
open var frame: CGRect
// 設定檢視在父座標系下的大小
open var bounds: CGRect
// 設定檢視在父檢視中的位置
open var center: CGPoint
// 通過此屬性可以修改物件的平移、縮放比例和旋轉角度
open var transform: CGAffineTransform複製程式碼

1、frame && bounds && center

測試程式碼如下:

// 測試 frame && bounds && center
func testFrame() {

    let view = UIView.init(frame: CGRect.init(x: 100, y: 200, width: 50, height: 50));
    self.view.addSubview(view);

    print(view.frame);
    print(view.bounds);
    print(view.center);
}複製程式碼

測試結果如下:

(100.0, 200.0, 50.0, 50.0)
(0.0, 0.0, 50.0, 50.0)
(125.0, 225.0)複製程式碼

由結果可知,bounds屬性只是提供了檢視物件的大小,並不包含位置!

2、transform

transform 是一個非常重要的屬性,它在矩陣變換的層面上改變檢視的顯示效果,完成旋轉、形變、平移等等操作。
偷個懶,這個屬性牽涉面較多,可以參看這篇文章

五、管理檢視層次結構

// 父檢視
open var superview: UIView? { get }
// 子檢視陣列
open var subviews: [UIView] { get }
// 該檢視所在的視窗檢視
open var window: UIWindow? { get }

// 從父檢視移除
open func removeFromSuperview()
// 在指定索引處插入某個子檢視
open func insertSubview(_ view: UIView, at index: Int)
// 交換兩個指定索引的子檢視
open func exchangeSubview(at index1: Int, withSubviewAt index2: Int)

// 新增檢視到子檢視列表的末尾    
open func addSubview(_ view: UIView)
// 在指定的子檢視之上插入某個子檢視
open func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView)
// 在指定的子檢視之下插入某個子檢視
open func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView)

// 移動某個子檢視到所有子檢視的最上方   
open func bringSubview(toFront view: UIView)
// 移動某個子檢視到所有子檢視的最下方 
open func sendSubview(toBack view: UIView)

// 
open func isDescendant(of view: UIView) -> Bool複製程式碼

測試程式碼如下:

// 測試 檢視的層次結構
func testHierarchicalStructure() {

    let view1 = self.createView(tag: 0);
    self.view.addSubview(view1);

    let view2 = self.createView(tag: 1);
    self.view.addSubview(view2);

    let view3 = self.createView(tag: 2);
    self.view.addSubview(view3);

    print("superView:
" + String(describing: view1.superview));
    self.printSubviews(name: "subviews");

    let view101 = self.createView(tag: 100);
    UIApplication.shared.keyWindow?.addSubview(view101);
    print("
window2:
" + String(describing: view101.window))

    view1.removeFromSuperview();
    self.printSubviews(name: "removeFromSuperview");

    self.view.insertSubview(view1, at: 1);
    self.printSubviews(name: "insertSubviewAtIndex");

    self.view.exchangeSubview(at: 0, withSubviewAt: 1);
    self.printSubviews(name: "exchangeSubview");

    let view4 = self.createView(tag: 3);
    self.view.addSubview(view4);
    self.printSubviews(name: "addSubview");


    let view5 = self.createView(tag: 4);
    self.view.insertSubview(view5, aboveSubview: view3);
    self.printSubviews(name: "insertSubview:aboveSubview");


    let view6 = self.createView(tag: 5);
    self.view.insertSubview(view6, belowSubview: view3);
    self.printSubviews(name: "insertSubview:belowSubview");

    self.view.bringSubview(toFront: view2);
    self.printSubviews(name: "bringSubview:toFront");

    self.view.sendSubview(toBack: view2);
    self.printSubviews(name: "sendSubview:Toback");

    print("
view2 && view3  isDescendant: " + String(view2.isDescendant(of: view3)));
    print("view2 && self.view isDescendant: " + String(view2.isDescendant(of: self.view)));
    print("self.view && view2 isDescendant: " + String(self.view.isDescendant(of: view2)));
    print("view2 && view2 isDescendant: " + String(view2.isDescendant(of: view2)));

}

func createView(tag: NSInteger) -> UIView {

    let view = UIView.init(frame: CGRect.init(x: 100, y: 100, width: 200, height: 200));
    view.tag = tag;

    return view;
}

func printSubviews(name: String) {

    print("
(name):
" + String(describing: self.view.subviews.map { (view: UIView) -> NSInteger in return view.tag; }));
}複製程式碼

結果如下:

superView:
Optional(>)

subviews:
[0, 1, 2]

window2:
Optional(; layer = >)

removeFromSuperview:
[1, 2]

insertSubviewAtIndex:
[1, 0, 2]

exchangeSubview:
[0, 1, 2]

addSubview:
[0, 1, 2, 3]

insertSubview:aboveSubview:
[0, 1, 2, 4, 3]

insertSubview:belowSubview:
[0, 1, 5, 2, 4, 3]

bringSubview:toFront:
[0, 5, 2, 4, 3, 1]

sendSubview:Toback:
[1, 0, 5, 2, 4, 3]
view2 && view3  isDescendant: false
view2 && self.view isDescendant: true
self.view && view2 isDescendant: false
view2 && view2 isDescendant: true複製程式碼

注意:isDescendant 這個屬性在對比兩個 View 的時候,前者是後者的同一 View 或子 view 才為 true

六、配置自動佈局行為

// 控制autoresizingMask模式的開啟與關閉
open var autoresizesSubviews: Bool
// 子檢視相對於父檢視的調整模式
open var autoresizingMask: UIViewAutoresizing
// 檢視計算最合適的size(容納子檢視)並返回
open func sizeThatFits(_ size: CGSize) -> CGSize
// 計算合適Size,並更改本檢視的size去包含子檢視
open func sizeToFit()
// 當一個view的bounds變化的時候用於決定其內容怎麼變化(變化模式)
open var contentMode: UIViewContentMode複製程式碼

1、autoresizingMask && autoresizesSubviews

public struct UIViewAutoresizing : OptionSet {

    public init(rawValue: UInt)

    // 自動調整view與父檢視左邊距,以保證右邊距不變
    public static var flexibleLeftMargin: UIViewAutoresizing { get }
    // 自動調整view的寬度,保證左邊距和右邊距不變
    public static var flexibleWidth: UIViewAutoresizing { get }
    // 自動調整view與父檢視右邊距,以保證左邊距不變
    public static var flexibleRightMargin: UIViewAutoresizing { get }
    // 自動調整view與父檢視上邊距,以保證下邊距不變
    public static var flexibleTopMargin: UIViewAutoresizing { get }
    // 自動調整view的高度,以保證上邊距和下邊距不變
    public static var flexibleHeight: UIViewAutoresizing { get }
    // 自動調整view與父檢視的下邊距,以保證上邊距不變
    public static var flexibleBottomMargin: UIViewAutoresizing { get }
}複製程式碼

結構體的各個屬性,如上所述,具體測試程式碼如下:

var firstView = UIView.init();

func testAutoresizingMask() {

    self.firstView.frame = CGRect.init(x: 20, y: 80, width: 200, height: 200);
    self.firstView.backgroundColor = UIColor.red;

    self.view.addSubview(self.firstView);

    let secondView = UIView.init(frame: CGRect.init(x: 10, y: 10, width: 180, height: 20));
    secondView.backgroundColor = UIColor.brown;
    secondView.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin];
    self.firstView.addSubview(secondView);

    let thirdView = UIView.init(frame: CGRect.init(x: 10, y: 40, width: 180, height: 20));
    thirdView.backgroundColor = UIColor.cyan;
    thirdView.autoresizingMask = .flexibleLeftMargin;
    self.firstView.addSubview(thirdView);

    let fourthView = UIView.init(frame: CGRect.init(x: 10, y: 70, width: 180, height: 20));
    fourthView.backgroundColor = UIColor.blue;
    fourthView.autoresizingMask = .flexibleRightMargin;
    self.firstView.addSubview(fourthView);

    let fifthView = UIView.init(frame: CGRect.init(x: 10, y: 110, width: 180, height: 50));
    fifthView.backgroundColor = UIColor.yellow;
    fifthView.autoresizingMask = [.flexibleTopMargin, .flexibleHeight];
    self.firstView.addSubview(fifthView);

    let changeBtn = UIButton.init(type: .custom);
    changeBtn.setTitle("更改frame", for: .normal);
    changeBtn.frame = CGRect.init(x: 20, y: 500, width: 120, height: 40);
    changeBtn.backgroundColor = UIColor.green;
    changeBtn.addTarget(self, action: #selector(changeAutoresizingMaskFrame), for: .touchUpInside);
    self.view.addSubview(changeBtn);

}

func changeAutoresizingMaskFrame() {

    let framesArray = [220,250,270,300,320];

    let index = Int(arc4random() % 5);

    self.firstView.frame = CGRect.init(x: 20, y: 80, width: framesArray[index], height: framesArray[index]);
}複製程式碼

不斷點選按鈕,得到如下圖結果:

由結果可知,API與其描述相符,設定 flexibleRight 則左側距離保持不變,設定 flexibleTop 則子View距離底部距離保持不變,即關鍵字(如 leftrighttopbottom)代表的反方向的相對距離保持不變。

接著上面的測試,新增如下程式碼:

self.firstView.autoresizesSubviews = false;複製程式碼

重複上面的測試方法,不斷點選按鈕,得到結果如下圖:

由上圖可知,之前設定的 autoresizingMask沒有效果了,autoresizesSubviews就是控制 autoresizingMask模式的開關,預設是開啟的。

2、sizeThatFits && sizeToFit

測試程式碼如下:

func testSizeFits() {

    let sizeLabel = UILabel.init(frame: CGRect.init(x: 20, y: 100, width: 0, height: 0));
    sizeLabel.font = UIFont.systemFont(ofSize: 20);
    sizeLabel.text = "王隆帥的簡書 王隆帥的部落格 王隆帥!!!";
    self.view.addSubview(sizeLabel);

    let fitSize = sizeLabel.sizeThatFits(CGSize.zero);
    print("sizeThatFits ------- 
" + String(describing: fitSize));
    print("sizeThatFits 後 Label的尺寸 ------- 
" + String(describing: sizeLabel.frame.size));

    sizeLabel.sizeToFit();
    print("sizeToFit 後 Label的尺寸 ------- 
" + String(describing: sizeLabel.frame.size));
}複製程式碼

結果如下:

sizeThatFits ------- 
(333.5, 24.0)
sizeThatFits 後 Label的尺寸 ------- 
(0.0, 0.0)
sizeToFit 後 Label的尺寸 ------- 
(333.5, 24.0)複製程式碼

由結果可知 sizeThatFits 方法得到的自適應後的尺寸,但是並沒有更改標籤的實際大小,而sizeToFit將自適應得到的尺寸(內部也是呼叫sizeThatFits獲取自適應尺寸)應用到相應的 label 上,是label的實際尺寸更改為自適應的尺寸。

3、contentMode

public enum UIViewContentMode : Int { 

    // 改變內容的高寬比例,縮放內容,UIView中完整顯示內容,填滿UIView 
    case scaleToFill
    // 保持內容的高寬比,縮放內容,完整顯示內容,最大化填充UIview,沒填充上的區域透明
    case scaleAspectFit 
    // 保持內容高寬比,縮放內容,超出檢視的部分內容會被裁減,填充UIView
    case scaleAspectFill
    // 當View的bounds改變,系統會呼叫setNeedsDisplay,重新繪製檢視
    case redraw 
    // 不縮放,內容在檢視中間
    case center 
    // 不縮放,內容在檢視頭部
    case top
    // 不縮放,內容在檢視底部
    case bottom
    // 不縮放,內容在檢視左側
    case left
    // 不縮放,內容在檢視右側
    case right
    // 不縮放,內容在檢視頭部及左側
    case topLeft
    // 不縮放,內容在檢視頭部及右側
    case topRight
    // 不縮放,內容在檢視底部及左側
    case bottomLeft
    // 不縮放,內容在檢視底部及右側
    case bottomRight
}複製程式碼

測試程式碼就忽略不計了,具體效果如下圖(圖片來源是這裡):

七、待續…

篇幅所限,因為UIView的內容略多,所以接下來會分篇來整理。

相關文章