玩转swift -- UIKit 之 UIView(1)

96
王隆帅
2017.06.28 07:16* 字数 1981

概述

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<UITouch>, with event: UIEvent?) {
        
    touchNums = touchNums + touches.count;
    print(touchNums);
}
    
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        
    touchNums = touchNums - touches.count;
    print(touchNums);
}
    
override func touchesCancelled(_ touches: Set<UITouch>, 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:\n" + String(describing: view1.superview));
    self.printSubviews(name: "subviews");

    let view101 = self.createView(tag: 100);
    UIApplication.shared.keyWindow?.addSubview(view101);
    print("\nwindow2:\n" + 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("\nview2 && 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("\n\(name):\n" + String(describing: self.view.subviews.map { (view: UIView) -> NSInteger in return view.tag; }));
}
    

结果如下:

superView:
Optional(<UIView: 0x7ffe63104190; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x610000029fa0>>)

subviews:
[0, 1, 2]

window2:
Optional(<UIWindow: 0x7ffe5f707510; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x618000050da0>; layer = <UIWindowLayer: 0x61800002fbc0>>)

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 ------- \n" + String(describing: fitSize));
    print("sizeThatFits 后 Label的尺寸 ------- \n" + String(describing: sizeLabel.frame.size));
        
    sizeLabel.sizeToFit();
    print("sizeToFit 后 Label的尺寸 ------- \n" + 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的内容略多,所以接下来会分篇来整理。

玩转Swift