iOS文檔補完計劃--UIView

UIView可以說是我們日常工作中接觸最多的一個對象、是所有視圖控件(不包括視圖控制器)的基類。
主要的功能包括視圖樣式、層級、約束、自動佈局、渲染、手勢、動畫、座標轉換等等。

其中有些東西(比如原生自動佈局、而我們平時都用mas/sd)並不常用、所以只篩選了一部分平時可能用得到的地方。
由於內容實在太多、所以有些複雜的地方只是簡單總結一下並且給出一些參考鏈接方便查閱


目錄主要分爲以下幾個樣式:
常用、會用、瞭解

目錄

  • 創建視圖對象
  • 配置視圖的視覺外觀
    • backgroundColor
    • hidden
    • alpha
    • opaque
    • tintColor
    • tintAdjustmentMode
    • clipsToBounds
    • clearsContextBeforeDrawing
    • maskView
    • layerClass
    • layer
  • 配置與事件相關的行爲
    • userInteractionEnabled
    • multipleTouchEnabled
    • exclusiveTouch
  • 配置邊界和框架矩形
    • frame
    • bounds
    • center
    • transform
  • 管理視圖層次結構
    • superview
    • subviews
    • window
    • addSubview
    • removeFromSuperview
    • bringSubviewToFront
    • sendSubviewToBack
    • insertSubview:atIndex:
    • insertSubview:aboveSubview:
    • insertSubview: belowSubview:
    • exchangeSubviewAtIndex:withSubviewAtIndex:
    • isDescendantOfView:
  • 觀察與視圖層級的更改
    • didAddSubview/willRemoveSubview等六個方法
  • 配置內容邊距
    • LayoutMargins相關
  • 屏幕的安全區域
    • safeAreaInsets
    • safeAreaLayoutGuide
    • safeAreaInsetsDidChange
    • insetsLayoutMarginsFromSafeArea
  • 測量Auto Layout
    • systemLayoutSizeFittingSize
    • systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority
    • intrinsicContentSize
    • invalidateIntrinsicContentSize
    • Compression Resistance priority(抗壓縮)
    • Hugging priority(抗拉伸)
  • 觸發自動佈局
    • needsUpdateConstraints
    • setNeedsUpdateConstraints
    • updateConstraints
    • updateConstraintsIfNeeded
  • 配置調整大小行爲
    • contentMode
    • UIViewContentMode
    • sizeThatFits
    • sizeToFit
    • autoresizesSubviews
    • autoresizingMask
  • 佈局子視圖
    • layoutSubviews
    • setNeedsLayout
    • layoutIfNeeded
    • requiresConstraintBasedLayout
    • translatesAutoresizingMaskIntoConstraints
  • 繪製和更新視圖
    • drawRect
    • setNeedsDisplay
    • setNeedsDisplayInRect
    • contentScaleFactor
    • tintColorDidChange
  • 管理手勢識別器
    • 添加刪除和獲取
    • gestureRecognizerShouldBegin
  • 觀察焦點
    • canBecomeFocused
    • focused
  • 運動視覺效果
    • 添加刪除和獲取
  • 後臺啓動恢復
  • 捕獲視圖快照
  • 識別視圖
    • tag
    • viewWithTag
  • 座標系轉換
    • convertPoint
    • convertRect
    • 超出父視圖的View可以被點擊
  • 命中測試(Hit-Testing)
    • hitTest:withEvent
    • pointInside:withEvent
    • 爲響應鏈尋找最合適的FirstView
  • 結束視圖編輯
    • endEditing:
  • Block動畫
  • 首尾式動畫

UIView

包含了UIView的基本功能

  • userInteractionEnabled

設置用戶交互,默認YES允許用戶交互

@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
  • tag

控件標記(父控件可以通過tag找到對應的子控件),默認爲0

@property(nonatomic)  NSInteger tag; 
  • 觀察焦點
  • 管理用戶界面方向

semanticContentAttribute(翻轉效果)

是否翻轉視圖

@property (nonatomic) UISemanticContentAttribute semanticContentAttribute NS_AVAILABLE_IOS(9_0);

世上總是有很多奇人異事、比如阿拉伯人的閱讀順序。
可以讓佈局自動左右翻轉、不過前提是佈局時使用Leading以及Trailing兩個約束條件而不是Left和Right。

獲取視圖方向

/** 獲取視圖的方向 */
+ (UIUserInterfaceLayoutDirection)userInterfaceLayoutDirectionForSemanticContentAttribute:(UISemanticContentAttribute)attribute NS_AVAILABLE_IOS(9_0);
 
/** 獲取相對於指定視圖的界面方向 */
+ (UIUserInterfaceLayoutDirection)userInterfaceLayoutDirectionForSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute relativeToLayoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection NS_AVAILABLE_IOS(10_0);
 
/** 返回即時內容的佈局的方向 */
@property (readonly, nonatomic) UIUserInterfaceLayoutDirection effectiveUserInterfaceLayoutDirection NS_AVAILABLE_IOS(10_0);


UIViewGeometry(幾何分類)

  • multipleTouchEnabled

是否允許多點觸摸 默認NO

@property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled;

創建視圖對象

/** 通過Frame初始化UI對象 */
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
/** 用於xib初始化 */
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

配置視圖的視覺外觀

  • backgroundColor

視圖背景色

@property(nonatomic, copy) UIColor *backgroundColor;

默認值nil、也就是透明。

  • hidden

是否隱藏視圖

@property(nonatomic, getter=isHidden) BOOL hidden;

父視圖的隱藏會導致子視圖被隱藏。並且不能被點擊、不能成爲第一響應者

  • alpha

視圖透明度

@property(nonatomic) CGFloat alpha;

父視圖的透明度會應用到子視圖上。小於0.01則等於被隱藏了。

  • opaque

視圖是否不透明。主要是用於視圖混合

@property(nonatomic, getter=isOpaque) BOOL opaque;

UIView的默認值是YES、但UIButton等子類的默認值都是NO。
需要注意他並不是我們肉眼層面的透明。

這和視圖合成機制有關
簡而言之如果你的視圖alpha=1、完全可以將opaque=YES。讓GPU在混合視圖時不必考慮下方視圖顏色。

  • tintColor

色調顏色

@property(nonatomic, strong) UIColor *tintColor;
  1. 父視圖更改了tintColor爲red,那麼它所有的一級子視圖tintColor全部爲red。下一級也會根據前一級進行設置。
    除非你主動設置了子視圖的tintColor。
  2. 原生控件基本都有默認的tintColor、比如UIButton爲藍色
    當然、也和創建的方式有關。UIButtonTypeCustom方式是沒有的。
  3. 當tintColor被修改、將會調用對象的tintColorDidChange:方法。
    個人感覺就這個玩意比較有用、畢竟我們更多的是自定義控件着色。不可能希望全屏幕變成一個顏色。

想進一步瞭解的話。這裏推薦一個博客可以看一看: 《iOS tintColor解析》

  • tintAdjustmentMode

色調(tintColor)模式

typedef NS_ENUM(NSInteger, UIViewTintAdjustmentMode) {
    UIViewTintAdjustmentModeAutomatic,//視圖的着色調整模式與父視圖一致
    
    UIViewTintAdjustmentModeNormal,//默認值
    UIViewTintAdjustmentModeDimmed,//暗色
} NS_ENUM_AVAILABLE_IOS(7_0);

@property(nonatomic) UIViewTintAdjustmentMode tintAdjustmentMode NS_AVAILABLE_IOS(7_0);

改變這個屬性、也會調用tintColorDidChange:方法。

  • clipsToBounds

是否截取掉超過子視圖超過自身的部分、默認爲NO

@property(nonatomic)  BOOL  clipsToBounds; 

最大的用處還是切圓角和圖片吧。
需要注意的是layer有一個方法maskToBounds也是一個作用、clipsToBounds內部就是調用了maskToBounds
其實效果一樣、只不過從語義上來講分成Viewlayer兩個方法。

  • clearsContextBeforeDrawing

視圖重繪前是否先清理以前的內容,默認YES

@property(nonatomic)  BOOL   clearsContextBeforeDrawing;

如果你把這個屬性設爲NO、那麼你要保證能在 drawRect:方法中正確的繪畫。
如果你的代碼已經做了大量優化、那麼設爲NO可以提高性能、尤其是在滾動時可能只需要重新繪畫視圖的一部分。
所以說、通常用不到。

  • maskView

遮罩層

@property(nullable, nonatomic,strong) UIView *maskView NS_AVAILABLE_IOS(8_0);
  1. 雖說是遮罩層、但實際上不會多出一個View。
    只是對顏色的混合有影響。
  2. 只會顯示出與maskView可見(不透明)部分重疊的部分。
  3. maskView的對應點的alpha會賦值給View對應的point。
  4. 與layer.mask基本相同、只是需要8.0的支持。

舉一個簡單的例子:

UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(0, 100, 200, 200)];
view1.backgroundColor = [UIColor blueColor];

UIView * maskView = [[UIView alloc]initWithFrame:view1.bounds];
maskView.backgroundColor = [UIColor clearColor];


UIView * view_1 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 200)];
view_1.backgroundColor = [UIColor whiteColor];
[maskView addSubview:view_1];

UIView * view_2 = [[UIView alloc]initWithFrame:CGRectMake(100, 0, 100, 200)];
view_2.backgroundColor = [UIColor whiteColor];
view_2.alpha = 0.5;
[maskView addSubview:view_2];

view1.maskView = maskView;
[self.view addSubview:view1];

你還可以通過layer或者圖片來設計出很多有趣的效果:《使用 maskView 設計動畫》《還有一個有趣的動畫庫》(layer.mask)

  • layerClass

返回當前View所使用的根Layer類型

#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) Class layerClass;                        // default is [CALayer class]. Used when creating the underlying layer for the view.
#else
+ (Class)layerClass;                        // default is [CALayer class]. Used when creating the underlying layer for the view.
#endif

layer有很多種、比如CATextLayer適合文本、CAGradientLayer適合處理漸變、CAReplicatorLayer適合處理很多相似的圖層。

當然這些我都不太瞭解~你可以參閱《[iOS Animation]-CALayer 專用圖層》

  • layer

layer視圖圖層(可以用來設置圓角效果/陰影效果)

@property(nonatomic,readonly,strong) CALayer  *layer;  

配置與事件相關的行爲

  • userInteractionEnabled

設置用戶交互、默認YES允許用戶交互。

@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;

這個屬性直接影響到控件能否進入響應鏈或者成爲第一響應者。
《iOS文檔補完計劃--UIResponder》

  • multipleTouchEnabled

是否允許多指觸控。默認NO

@property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled __TVOS_PROHIBITED;
  • exclusiveTouch

是否讓View獨佔Touch事件

@property(nonatomic,getter=isExclusiveTouch) BOOL       exclusiveTouch;

默認是NO。設置成YES避免在一個界面上同時點擊多個button。


配置邊界和框架矩形

  • frame

位置和尺寸

@property(nonatomic) CGRect  frame;

以父控座標系的左上角爲座標原點(0, 0)

  • bounds

位置和尺寸

@property(nonatomic) CGRect    bounds;

以自身標系的左上角爲座標原點(0, 0)

  • center

中心點

@property(nonatomic) CGPoint   center;

以父控件的左上角爲座標原點(0, 0)

  • transform

形變

@property(nonatomic) CGAffineTransform transform;

可以做一些Transform動畫、大概就是形變、縮放、位移咯。
比如你可以給cell做一些小動畫

NSArray *cells = tableView.visibleCells;
for (int i = 0; i < cells.count; i++) {
    UITableViewCell *cell = [cells objectAtIndex:i];
    if (i%2 == 0) {
        cell.transform = CGAffineTransformMakeTranslation(-BSScreen_Width,0);
    }else {
        cell.transform = CGAffineTransformMakeTranslation(BSScreen_Width,0);
    }
    [UIView animateWithDuration:duration delay:i*0.03 usingSpringWithDamping:0.75 initialSpringVelocity:1/0.75 options:0 animations:^{
        cell.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        
    }];
}

管理視圖層次結構

  • superview

獲取父視圖

@property(nullable, nonatomic,readonly) UIView *superview;
  • subviews

獲取所有子視圖

@property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *subviews;

數組的順序等於添加到父視圖上的順序。
你也可以嘗試用遞歸的方式遍歷所有子視圖嗯。

  • window

獲取視圖所在的Window

@property(nullable, nonatomic,readonly) UIWindow  *window;

這個我也不太懂幹嘛的。只知道如果父視圖是UIWindow一定有值、否則(我測試的都是)爲空。

  • - addSubview:

添加子視圖

- (void)addSubview:(UIView *)view;

會被添加在subviews的末尾、視圖層級的最上方。

  • - removeFromSuperview

從父視圖上移除

- (void)removeFromSuperview;
  • - bringSubviewToFront:

移動指定的子視圖,使其顯示在其所以兄弟節點之上

- (void)bringSubviewToFront:(UIView *)view;
  • - sendSubviewToBack:

移動指定的子視圖,使其顯示在其所有兄弟節點之下

- (void)sendSubviewToBack:(UIView *)view;

自己試去吧~

UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(10, 150, 100, 50)];
view1.backgroundColor = [UIColor blueColor];
[self.view addSubview:view1];

UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(15, 155, 100, 50)];
view2.backgroundColor = [UIColor grayColor];
[self.view addSubview:view2];

UIView *view3 = [[UIView alloc] initWithFrame:CGRectMake(20, 160, 100, 50)];
view3.backgroundColor = [UIColor yellowColor];
[self.view addSubview:view3];

//如果將下面兩行代碼都註釋掉   view1 會在下面   view2會在上面
//  下面這行代碼能夠將view2  調整到父視圖的最下面
//    [self.view sendSubviewToBack:view2];
//將view調整到父視圖的最上面
[self.view bringSubviewToFront:view1];
  • - insertSubview:atIndex:

插入子視圖(將子視圖插入到subviews數組中index這個位置)

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
  • - insertSubview:aboveSubview:

插入子視圖(將子視圖插到siblingSubview之上)

- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;
  • - insertSubview: belowSubview:

插入子視圖(將子視圖插到siblingSubview之下)

- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;
  • - exchangeSubviewAtIndex:withSubviewAtIndex:

交換兩個子視圖

- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;
  • - isDescendantOfView:

檢測一個視圖是否屬於另一個的子視圖

- (BOOL)isDescendantOfView:(UIView *)view;

舉個例子:

(lldb) po [self.view isDescendantOfView:view1]
NO
(lldb) po [view1 isDescendantOfView:self.view]
YES

需要注意的是、這個判定並不侷限於一級結構。


觀察與視圖層級的更改

包含子視圖的成功添加/移除、本視圖添加到新父視圖的開始/結束。

一下方法默認不做任何操作。需要注意的是在removeFromSuperview時、也會調用。只不過newSuperview爲空。

/** 添加自視圖完成後調用 */
- (void)didAddSubview:(UIView *)subview;
/** 將要移除自視圖時調用 */
- (void)willRemoveSubview:(UIView *)subview;
 
/** 將要移動到新父視圖時調用 */
- (void)willMoveToSuperview:(nullable UIView *)newSuperview;
/** 移動到新父視圖完成後調用 */
- (void)didMoveToSuperview;
/** 將要移動到新Window時調用 */
- (void)willMoveToWindow:(nullable UIWindow *)newWindow;
/** 移動到新Window完成後調用 */
- (void)didMoveToWindow;

配置內容邊距

官方文檔對Content Margin的解釋

  • directionalLayoutMargins

iOS11 開始引入,可以根據語言的方向進行前後佈局,與 layoutMargins 相比,能更好的適配 RTL 語言。

@property (nonatomic) NSDirectionalEdgeInsets directionalLayoutMargins API_AVAILABLE(ios(11.0),tvos(11.0));

但和我們關係不大、除非某天我們進軍中東的某些國家了。

  • layoutMargins

自動佈局時。用於指定視圖和它的子視圖之間的邊距。

@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);

iOS11之後請使用directionalLayoutMargins屬性進行佈局。他將左右的概念替換成了前後。而且這兩個屬性會互相同步

默認爲8個單位。如果視圖並不是完全處於安全區域內或者設置了preservesSuperviewLayoutMargins則可能更大。

不過說實話如果用masonry的話感覺這個屬性意義不大

  • preservesSuperviewLayoutMargins

是否將當前視圖的間距和父視圖相同。

@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0);

設置一個視圖的邊距(視圖邊緣與其子視圖邊緣的距離)、防止其子視圖和父視圖邊緣重合。

  • -layoutMarginsDidChange

改變view的layoutMargins這個屬性時,會觸發這個方法

- (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);

用原生Layout來佈局說實話我沒用過。可以看看這篇帖子:《layoutMargins和preservesSuperviewLayoutMargins》


屏幕的安全區域

官方文檔對的《Safe Area》解釋

iOS11之後、(爲了幫助適配iPhoneX?)蘋果給我們引入了一個安全區域的概念。
安全區域幫助我們將view放置在整個屏幕的可視的部分。即使把navigationbar設置爲透明的,系統也認爲安全區域是從navigationbar的bottom開始,保證不被系統的狀態欄、或導航欄覆蓋。

  • safeAreaInsets

反映了一個view距離該view的安全區域的邊距

@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));

這個屬性會被系統在佈局後自動設置。比如你可以在UIViewControllerviewSafeAreaInsetsDidChange方法下檢測橫屏以及豎屏下的變化。
對於UIView也有對應的方法safeAreaInsetsDidChange

  • safeAreaLayoutGuide

safeAreaLayoutGuide是一個相對抽象的概念,爲了便於理解,我們可以把safeAreaLayoutGuide看成是一個“view”,這個“view”系統自動幫我們調整它的bounds,讓它不會被各種奇奇怪怪的東西擋住,包括iPhone X的劉海區域和底部的一道槓區域,可以認爲在這個“view”上一定能完整顯示所有內容。

結合Masonry的用法:將四周與安全區域貼合

[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
    make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
    make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
    make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
}];
  • - safeAreaInsetsDidChange

當View的safeAreaInsets發生變化時自動調用

- (void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));
  • insetsLayoutMarginsFromSafeArea

決定在自動佈局時是否考慮safeAreaInsets的限制

@property (nonatomic) BOOL insetsLayoutMarginsFromSafeArea

示例代碼:

self.view.insetsLayoutMarginsFromSafeArea = NO;
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.backgroundColor = [UIColor orangeColor];
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tableView];

NSArray<__kindof NSLayoutConstraint *> *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[tableView]-|" options:0 metrics:nil views:@{@"tableView" : self.tableView}];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tableView]|" options:0 metrics:nil views:@{@"tableView" : self.tableView}];
[self.view addConstraints:constraints];

關於iOS11安全區域的特性、推薦一篇Bugly的帖子《iOS 11 安全區域適配總結》


測量Auto Layout

  • - systemLayoutSizeFittingSize:

返回Auto Layout後內容高度。

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);

通常用於包含多層視圖的控件計算(比如cell)。這裏注意與sizeThatFitssizeToFits進行區分。

UILabel *label = [[UILabel alloc] init];
label.text = @"SafeAreaS";
label.numberOfLines = 0;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor greenColor];
[self.view addSubview:label];

[label mas_makeConstraints:^(MASConstraintMaker *make) {
    if (@available(iOS 11, *)) {
        make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
        make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
    } else {
        make.top.equalTo(self.mas_topLayoutGuideBottom);
        make.left.mas_equalTo(0);
    }

}];

[self.view layoutIfNeeded];
CGSize layoutSize =  [label systemLayoutSizeFittingSize:UILayoutFittingExpandedSize];
CGSize labelSize = label.frame.size;

//(lldb) po layoutSize
//(width = 79.666666666666671, height = 20.333333333333332)
//
//(lldb) po labelSize
//(width = 79.666666666666671, height = 20.333333333333332)

利用這個方法、我們可以對是適應高度的Cell高度進行緩存來適配iOS8以下的情況。需要注意的是要對cell.contentView進行計算。

model.cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
  • systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:

和上一個方法一樣、但是增加了寬高的優先級。使結果更加準確

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority NS_AVAILABLE_IOS(8_0);
  • intrinsicContentSize

返回控件的固有大小

#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) CGSize intrinsicContentSize NS_AVAILABLE_IOS(6_0);
#else
- (CGSize)intrinsicContentSize NS_AVAILABLE_IOS(6_0);
#endif

在自動佈局時、有些控件(UILabel/UIButton/UIImageView等)只需要設置位置而不需要設置大小、就是利用這個屬性。

當內容改變時、調用invalidateIntrinsicContentSize通知系統、並且自定義intrinsicContentSize的實現、來返回合適的寬高。

舉個例子、你可以讓你的UITextField能夠自適應寬度《一個隨輸入文字寬度變化的自定義UITextField》

  • - invalidateIntrinsicContentSize

廢除視圖原本內容的intrinsicContentSize

- (void)invalidateIntrinsicContentSize NS_AVAILABLE_IOS(6_0); 

上面已經說過用法了

  • 抗壓縮與抗拉伸
Compression Resistance priority(抗壓縮)

有多大的優先級阻止自己變小

/* 返回某個方向的抗壓縮等級 */
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
/* 設置某個方向的抗壓縮等級*/
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
Hugging priority(抗拉伸)

有多大的優先級阻止自己變大

/* 返回某個方向上的抗拉伸等級 */
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
/* 設置某個方向上的抗拉伸等級*/
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);

通常是給UILabel用的《HuggingPriority和CompressionResistance 一個例子教你理解》


觸發自動佈局

  • - needsUpdateConstraints

通知用戶是否有需要更新的約束

- (BOOL)needsUpdateConstraints NS_AVAILABLE_IOS(6_0);

這個可以作爲你判斷是否應該在後續代碼裏主動調用updateConstraints的前提。不過通常我們都不太需要、因爲佈局的修改都是自己寫的自然清楚何時調用。
還有就是當View尚未展示時、更新的標記一直會返回YES。

  • - setNeedsUpdateConstraints

標記準備更改約束

- (void)setNeedsUpdateConstraints NS_AVAILABLE_IOS(6_0);

通常是用作批量修改約束時的優化、避免系統多次計算。

  • - updateConstraints

當View本體的佈局被修改時被自動調用

- (void)updateConstraints NS_AVAILABLE_IOS(6_0) NS_REQUIRES_SUPER;

你可以在這裏自己更新View內部控件的佈局、需要調用super實現。
會被調用的情況:

  1. 視圖被添加到父視圖上
  2. 主動調用updateConstraintsIfNeeded
  3. 更新約束時
這裏需要注意的是更新約束後自動調用的時機:
  1. 如果你使用NSLayoutConstraint對約束進行更新
    updateConstraints方法會自動調用
  2. 如果使用Masonry對約束進行更新
    updateConstraints方法不會自動調用
    你需要手動setNeedsUpdateConstraints然後updateConstraintsIfNeeded
  • - updateConstraintsIfNeeded

立即觸發約束更新,自動更新佈局

- (void)updateConstraintsIfNeeded NS_AVAILABLE_IOS(6_0);

會把之前setNeedsUpdateConstraints標記之後的所有約束脩改、同時更新。
並且(如果控件已經被setNeedsUpdateConstraints標記)自動調用控件的updateConstraints方法。

所以、方法調用的順序爲:

視圖的佈局改變時:updateConstraints被執行。
視圖佈局修改:setNeedsUpdateConstraints-->updateConstraintsIfNeeded
而當setNeedsUpdateConstraints被調用needsUpdateConstraints也會返回YES作爲標記。

貼一個Masoney動畫的例子:《Masonry自動佈局詳解二:動畫更新約束》
但是這個帖子有些問題

// 告訴self.view約束需要更新
[self.view setNeedsUpdateConstraints];
// 調用此方法告訴self.view檢測是否需要更新約束,若需要則更新,下面添加動畫效果才起作用
[self.view updateConstraintsIfNeeded];

這兩句話並沒什麼必要、因爲我並沒有更新self.view的約束。直接修改成這樣、是比較好的。

- (void)onGrowButtonTaped:(UIButton *)sender {
    self.scacle += 0.5;

    [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
        make.center.mas_equalTo(self.view);
        
        // 初始寬、高爲100,優先級最低
        make.width.height.mas_equalTo(100 * self.scacle);
        // 最大放大到整個view
        make.width.height.lessThanOrEqualTo(self.view);
    }];
    
    [UIView animateWithDuration:0.3 animations:^{
        [self.view layoutIfNeeded];
    }];
}

這也佐證了一個問題。layoutIfNeeded執行動畫與setNeedsUpdateConstraints/updateConstraintsIfNeeded並沒什麼直接關係。


配置調整大小行爲

  • contentMode

內容顯示的模式。默認`UIViewContentModeScaleToFill

@property(nonatomic) UIViewContentMode contentMode;  

我們平時調整圖片顯示狀態用的就是這個屬性

  • UIViewContentMode

內容具體的顯示模式

typedef NS_ENUM(NSInteger, UIViewContentMode) {
    UIViewContentModeScaleToFill,       //!< 縮放內容到合適比例大小.
    UIViewContentModeScaleAspectFit,    //!< 縮放內容到合適的大小,邊界多餘部分透明.
    UIViewContentModeScaleAspectFill,   //!< 縮放內容填充到指定大小,邊界多餘的部分省略.
    UIViewContentModeRedraw,            //!< 重繪視圖邊界 (需調用 -setNeedsDisplay).
    UIViewContentModeCenter,            //!< 視圖保持等比縮放.
    UIViewContentModeTop,               //!< 視圖頂部對齊.
    UIViewContentModeBottom,            //!< 視圖底部對齊.
    UIViewContentModeLeft,              //!< 視圖左側對齊.
    UIViewContentModeRight,             //!< 視圖右側對齊.
    UIViewContentModeTopLeft,           //!< 視圖左上角對齊.
    UIViewContentModeTopRight,          //!< 視圖右上角對齊.
    UIViewContentModeBottomLeft,        //!< 視圖左下角對齊.
    UIViewContentModeBottomRight,       //!< 視圖右下角對齊.
};
  • - sizeThatFits:

計算內容最合適的大小、但並不改變view的size

- (CGSize)sizeThatFits:(CGSize)size;

通常用於leaf-level views、這裏注意與systemLayoutSizeFittingSize進行區分。

這裏的參數size、類似於UILabel的preferredMaxLayoutWidth屬性用於限制計算範圍。

你可以自己試試

UITextView * textView = [[UITextView alloc]initWithFrame:CGRectMake(0, 100, 20, 20)];
[self.view addSubview:textView];
//textView.text = @"asdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nasasdasdasdasdasdasdasda\nas";

textView.text = @"asdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasdaasdasdasdasdasdasdasda";
textView.backgroundColor = [UIColor orangeColor];


CGSize s = [textView sizeThatFits:CGSizeMake(self.view.bounds.size.width, 999)];
textView.frame = CGRectMake(textView.frame.origin.x, textView.frame.origin.y, s.width, s.height);

如果我們想讓UITextField等控件也自適應、重寫intrinsicContentSize內部用sizeThatFits:CGSizeMake計算一下就好了唄。

  • - sizeToFit

計算內容最合適的大小、並改變view的size

- (void)sizeToFit;

會使用控件原有的寬高進行計算、等價於:

CGSize s = [textView sizeThatFits:textView.frame.size];
textView.frame = CGRectMake(textView.frame.origin.x, textView.frame.origin.y, s.width, s.height);

注意如果修改了size、會調用layoutSubviews

  • autoresizesSubviews

當本身大小發生改變時、是否自動佈局子視圖

@property(nonatomic) BOOL  autoresizesSubviews;

如果視圖的autoresizesSubviews屬性聲明被設置爲YES,則其子視圖會根據autoresizingMask屬性的值自動進行尺寸調整。

  • autoresizingMask

當父視圖autoresizesSubviewsYES並且改變了大小時、該子視圖的佈局規則。

@property(nonatomic) UIViewAutoresizing autoresizingMask;

現在基本都是Auto Layout、很少用到這兩個東西了。
可以瞭解一下《IOS-AutoresizesSubviews》


佈局子視圖

  • - layoutSubviews

當控件被(系統)賦予了一個新的大小時觸發。

- (void)layoutSubviews;
  1. 添加到屏幕時觸發
    必須有指定的rect

  2. 調用setNeedsLayout時觸發

  3. size發生改變時觸發。
    觸發次數需要滿足最後兩點規則

  4. 滑動scrollview時觸發

  5. 旋轉屏幕時觸發

  6. 系統賦予大小
    我們都知道用戶所設置的frame/layout並不會直接修改控件frame、而是會在特定的週期由系統進行佈局繪製。也就是說、我們在一個週期內連續設置多次frame/layout、系統也只會在週期結束時佈局一次、並觸發一次layoutSubviews

  7. 控件只有被添加到屏幕上、才能觸發layoutSubviews。
    通常都會觸發兩次、因爲你還得給他設置frame/layout。
    不過如果先設置frame然後隔了很久才添加到屏幕上、就是一次。

  • - setNeedsLayout

爲該控件設置標記。等待更新佈局

- (void)setNeedsLayout;

setNeedsUpdateConstraints的機制一樣、他允許你對其多個子View進行佈局後統一更新。

注意它並不是實時更新、而會在下一次佈局週期中進行統一更新。

  • - layoutIfNeeded

立即更新該View所有子視圖的佈局

- (void)layoutIfNeeded;

它會將所有尚未更新的佈局立即進行更新。

通常的用處有兩個(歡迎補充):
1.佈局尚未完成、但我們需要獲取具體的Frame(當然、根據情況你也可以使用systemLayoutSizeFittingSize以節省性能)

UIView * view = [UIView new];

[self.view addSubview:view];

[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.right.equalTo(self.view);
    make.height.mas_equalTo(50);
}];

CGSize s = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
NSLog(@"%lf",s.height);//50.000000

NSLog(@"%lf",view.frame.size.height);//0.000000
[self.view layoutIfNeeded];
NSLog(@"%lf",view.frame.size.height);//50.000000
  1. 讓約束通過動畫更新
    貼一個Masoney動畫的例子:《Masonry自動佈局詳解二:動畫更新約束》
  • requiresConstraintBasedLayout

標記View是否需要用AutoLayout進行佈局

#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) BOOL requiresConstraintBasedLayout NS_AVAILABLE_IOS(6_0);
#else
+ (BOOL)requiresConstraintBasedLayout NS_AVAILABLE_IOS(6_0);
#endif

這個說實話不太懂、看文檔和網上的意思都是如果不返回YES、有可能不調用updateConstraints方法。但是我自己寫起來總會調用、希望有大神指正。
至於爲什麼要調用updateConstraints:《masonry小問題之requiresConstraintBasedLayout》

  • translatesAutoresizingMaskIntoConstraints

是否將AutoresizingMask轉化成Constraints約束。默認爲YES

@property(nonatomic) BOOL translatesAutoresizingMaskIntoConstraints NS_AVAILABLE_IOS(6_0);

名字已經把這個屬性的作用說的很明白了、但我們還可以再解釋一下:

  1. 當我們用代碼添加視圖時:
    視圖的translatesAutoresizingMaskIntoConstraints屬性默認爲YES,可是AutoresizingMask屬性默認會被設置成None。也就是說如果我們不去動AutoresizingMask,那麼AutoresizingMask就不會對約束產生影響。
  2. 當我們使用interface builder添加視圖時:
    AutoresizingMask雖然會被設置成非None,但是translatesAutoresizingMaskIntoConstraints默認被設置成了NO。所以也不會有衝突。
  3. 會出現問題的情況:
    當有一個視圖是靠AutoresizingMask佈局的,而我們修改了translatesAutoresizingMaskIntoConstraints後會讓視圖失去約束,走投無路。例如我自定義轉場時就遇到了這樣的問題,轉場後的視圖並不在視圖的正中間。

不過這些問題似乎只有用NSLayoutConstraint才能體現出問題?

參考《AutoLayout的那些事兒》《代碼添加constraint,設置translatesAutoresizingMaskIntoConstraints爲NO的原因》


繪製和更新視圖

  • - drawRect:

自定義的繪製內容

- (void)drawRect:(CGRect)rect;

1.如果是UIView則不需要調用super、UIView子類需要調用super實現。

  1. 不要手動調用。
    可以通過setNeedsDisplay或者setNeedsDisplayInRect讓系統自己調用。
  2. UIImageView不允許使用重寫draw繪製
    因爲他本身也不使用draw繪製、僅僅是使用內部的image view來顯示圖像
  3. 如果自己實現了drawRect、那務必每次都實現它
    setNeedsDisplay會將繪製全部清空。系統自動調用時也是。
    setNeedsDisplayInRect則會清空指定的rect。
調用的時機:

1. 視圖第一次被添加到父視圖上的時候、由系統自動調用
需要注意及時hidden = YES或者alpha = 0也會調用、但remove後再add並不會。
2. 添加到父視圖時必須有給定的rect、纔會被自動調用
也就是size必須不爲{0,0}
3. 修改了rect被調用自動調用
4.setNeedsDisplay+setNeedsDisplayInRect
當然、必須有rect

具體使用:

在iOS中使用drawRect繪圖一般分爲以下5個步驟:
1、獲取繪圖上下文
CGContextRef context = UIGraphicsGetCurrentContext();
2、創建並設置路徑
3、將路徑添加到上下文
如:線寬、線條顏色、填充顏色等
4、設置上下文狀態
5、繪製路徑
6、釋放路徑

具體可以參考:《drawRect的繪製的使用(繪製文本字符、繪製圖片、繪製圖形)》

使用DrawRect是會有一定性能問題的:

  1. contents寄宿圖
    《內存惡鬼drawRect》
  2. 離屏渲染
    《深刻理解移動端優化之離屏渲染》

這塊我有空準備補一下、先打個卡:
《iOS繪製和渲染》

  • - setNeedsDisplay

標記全部重繪

- (void)setNeedsDisplay;

需要注意的是並不會立即重繪、而是等到下一個週期

  • - setNeedsDisplayInRect:

標記指定rect重繪

- (void)setNeedsDisplayInRect:(CGRect)rect;

需要注意的是並不會立即重繪、而是等到下一個週期。

  • contentScaleFactor

視圖內容的縮放比例

@property(nonatomic) CGFloat  contentScaleFactor NS_AVAILABLE_IOS(4_0);

修改contentScaleFactor可以讓UIView的渲染精度提高,這樣即使在CGAffineTransform放大之後仍然能保持銳利

  • - tintColorDidChange

tintColor或者tintAdjustmentMode被修改時系統調用

- (void)tintColorDidChange NS_AVAILABLE_IOS(7_0);

你可以把他當成一個監聽來使用

- (void)tintColorDidChange {
  _tintColorLabel.textColor = self.tintColor;
  _tintColorBlock.backgroundColor = self.tintColor;
}

管理手勢識別器

  • 添加刪除和獲取
** 當前視圖所附加的所有手勢識別器 */
@property(nullable, nonatomic,copy) NSArray<__kindof UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
 
/** 添加一個手勢識別器 */
- (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer NS_AVAILABLE_IOS(3_2);
/** 移除一個手勢識別器 */
- (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer NS_AVAILABLE_IOS(3_2);
  • - gestureRecognizerShouldBegin

是否繼續識別手勢。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer NS_AVAILABLE_IOS(6_0);

此方法在gesture recognizer視圖轉出UIGestureRecognizerStatePossible狀態時調用,如果返回NO,則轉換到UIGestureRecognizerStateFailed;如果返回YES,則繼續識別觸摸序列.(默認情況下爲YES)。

你可以用來在控件指定的位置使用手勢識別
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
        CGPoint translation = [pan translationInView:self.view];
        CGFloat offsetX = self.scrollView.contentOffset.x;
        if (translation.x > 0 && offsetX == 0.0 ) {
               return NO;
        }
    }
    return YES;
}

觀察焦點

canBecomeFocused

返回是否可以成爲焦點, 默認NO

#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic,readonly) BOOL canBecomeFocused NS_AVAILABLE_IOS(9_0); // NO by default
#else
- (BOOL)canBecomeFocused NS_AVAILABLE_IOS(9_0); // NO by default
#endif

焦點是給AppleTV用的、用遙控器選擇屏幕上的控件。

在一個以焦點爲基礎的交互模型中,在屏幕上的單一視圖可以得到焦點,並且用戶可以通過瀏覽屏幕上不同的UI選項將焦點移動到其他視圖,從而引起焦點更新。得到焦點的視圖被用作任何用戶操作的目標事件。例如,如果一個屏幕上的按鈕被選中,當由遙控器發送按鈕選擇事件時,目標事件將被觸發。

focused

返回是否已經被聚焦

@property (readonly, nonatomic, getter=isFocused) BOOL focused NS_AVAILABLE_IOS(9_0);

運動視覺效果

就是王者榮耀那種可以晃手機看背景圖的效果吧

  • 添加刪除和獲取

添加、刪除、查看、我沒用過知道就得了


/** 添加運動效果,當傾斜設備時視圖稍微改變其位置 */
- (void)addMotionEffect:(UIMotionEffect *)effect NS_AVAILABLE_IOS(7_0);
 
/** 移除運動效果 */
- (void)removeMotionEffect:(UIMotionEffect *)effect NS_AVAILABLE_IOS(7_0);
 
/** 所有添加的運動效果 */
@property (copy, nonatomic) NSArray<__kindof UIMotionEffect *> *motionEffects NS_AVAILABLE_IOS(7_0);

後臺啓動恢復

你可以讓視圖在後臺恢復時候仍然保持原有的樣子

/** 標示是否支持保存,恢復視圖狀態信息 */
@property (nullable, nonatomic, copy) NSString *restorationIdentifier NS_AVAILABLE_IOS(6_0);
/** 保存視圖狀態相關的信息 */
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder NS_AVAILABLE_IOS(6_0);
/** 恢復和保持視圖狀態相關信息 */
- (void) decodeRestorableStateWithCoder:(NSCoder *)coder NS_AVAILABLE_IOS(6_0);

UIView的例子就不舉了、看看UIViewController的挺好《iOS的App實現狀態恢復》

與添加定位或者播放音頻等真後臺方式不同、這個僅僅是恢復頁面。


捕獲視圖快照

  • - snapshotViewAfterScreenUpdates:

對某個視圖進行快照

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;

該方法有一個BOOL類型的參數、這個參數表示是否立即生成快照、還是在需要更新視圖的時候生成。

UIView *showView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, 100, 100)];

showView.backgroundColor = [UIColor redColor];

[self.view addSubview:showView];

self.vvvv = showView;

UIView *snap1 = [showView snapshotViewAfterScreenUpdates:NO];

snap1.center = self.view.center;

[self.view addSubview:snap1];

設置YES、會等到當前隊列的所有方法完成之後、纔會生成快照。
在設置NO的情況、延時生成快照、也能達到YES的效果、原理是一樣的。

  • - resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:

比上面的方法多了兩個參數、意味着你可以把視圖進行分割操作

- (UIView *)resizableSnapshotViewFromRect:(CGRect)rect 
                       afterScreenUpdates:(BOOL)afterUpdates 
                            withCapInsets:(UIEdgeInsets)capInsets;
  • - drawViewHierarchyInRect:afterScreenUpdates:

比之前的多了一個rect參數、其他並沒發現什麼去區別~

- (BOOL)drawViewHierarchyInRect:(CGRect)rect 
             afterScreenUpdates:(BOOL)afterUpdates;

關於afterUpdates參數:
儘量設置爲NO、否則如果視圖中途被釋放掉會殷勤crash。


識別視圖

  • tag

識別標識、默認爲0

@property(nonatomic) NSInteger tag;
  • - viewWithTag

範圍子View中某個tag的View

- (__kindof UIView *)viewWithTag:(NSInteger)tag;
  1. 搜索包括二級子視圖
  2. 以隊列的形式搜索、搜索到一個則返回。

座標系轉換

將一個View中的Rect或Point轉化到另一個View的座標系中

/** 將point由point所在視圖轉換到目標視圖view中,返回在目標視圖view中的point值 */
- (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
/** 將point由point所在視圖轉換到目標視圖view中,返回在目標視圖view中的point值 */
- (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
/** 將rect由rect所在視圖轉換到目標視圖view中,返回在目標視圖view中的rect */
- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
/** 將rect從view中轉換到當前視圖中,返回在當前視圖中的rect */
- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;

每種轉換都有兩個方法、但實際向作用都一樣。
無非是參數位置的區別~
將view1中的一個視圖、轉化到view2的座標系中

[view1 convertRect:view1.childView toView:view2];
[view2 convertRect:view1.childView fromView:view1];

需要注意的是view參數如果爲nil、則會返回基於當前window的座標。
並且、兩個view必須歸屬於同一個window。

比如讓超出父視圖的View可以被點擊
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
        for (UIView *subView in self.subviews) {
            //將point從self的座標系、映射給subView的座標系
            CGPoint myPoint = [subView convertPoint:point fromView:self];
            //判斷point是否在subView上
            if (CGRectContainsPoint(subView.bounds, myPoint)) {
                return subView;
            }
        }
    }
    return view;
}
需要注意的是

如果你想讓某個控件攔截某個事件、爲了保險起見儘量修改父視圖的hitTest方法、以免被其他原本能夠響應的控件捷足先登。


命中測試(Hit-Testing)

  • - hitTest:withEvent:

詢問事件在當前視圖中的響應者,同時又是作爲事件傳遞的橋樑

- (UIView *)hitTest:(CGPoint)point 
          withEvent:(UIEvent *)event;

上面例子中重載的方法便是這個。

以下幾種狀態的視圖無法響應事件:
  1. 不允許交互
    userInteractionEnabled = NO
  2. 隱藏
    hidden = YES 如果父視圖隱藏,那麼子視圖也會隱藏,隱藏的視圖3. 無法接收事件
    透明度
    alpha < 0.01 如果設置一個視圖的透明度<0.01,會直接影響子視圖的透明度。alpha:0.0~0.01爲透明。
默認情況下:
  1. 若當前視圖無法響應事件
    則返回nil
  2. 若當前視圖可以響應事件
    但無子視圖可以響應事件、則返回自身作爲當前視圖層次中的事件響應者
  3. 若當前視圖可以響應事件
    同時有子視圖可以響應、則返回子視圖層次中的事件響應者
其內部實現大致爲:
  1. 看自身能否響應時間
  2. 看點是否在自身上
  3. 看子視圖是否能夠響應
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //3種狀態無法響應事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; 
    //觸摸點若不在當前視圖上則無法響應事件
    if ([self pointInside:point withEvent:event] == NO) return nil; 
    //從後往前遍歷子視圖數組 
    int count = (int)self.subviews.count; 
    for (int i = count - 1; i >= 0; i--) 
    { 
        // 獲取子視圖
        UIView *childView = self.subviews[i]; 
        // 座標系的轉換,把觸摸點在當前視圖上座標轉換爲在子視圖上的座標
        CGPoint childP = [self convertPoint:point toView:childView]; 
        //詢問子視圖層級中的最佳響應視圖
        UIView *fitView = [childView hitTest:childP withEvent:event]; 
        if (fitView) 
        {
            //如果子視圖中有更合適的就返回
            return fitView; 
        }
    } 
    //沒有在子視圖中找到更合適的響應視圖,那麼自身就是最合適的
    return self;
}
  • - pointInside:withEvent:

判斷觸摸點是否在自身座標範圍內

- (BOOL)pointInside:(CGPoint)point 
          withEvent:(UIEvent *)event;

默認實現是若在座標範圍內則返回YES,否則返回NO。

所以之前那個超出範圍點擊的方法中

if (CGRectContainsPoint(subView.bounds, myPoint)) {
    return subView;
}

換成

if ([subView pointInside:myPoint withEvent:event]) {
    return subView;
}

也是一樣可行的。

  • 爲響應鏈尋找最合適的FirstView

從事件傳遞到APP中開始、尋找最合適的View
UIApplication -> UIWindow -> 父View -> 子view

  1. 逐級調用hitTest:withEvent方法
  2. hitTest:withEvent方法內部通過pointInside:withEvent:進行判斷。
    通過則返回自身

結束視圖編輯

  • - endEditing:

強制讓自身或者子視圖上的UIResponder放棄第一響應者。

- (BOOL)endEditing:(BOOL)force;

更多收起鍵盤的方式可以看看《iOS開發-關閉/收起鍵盤方法總結》


Block動畫

傳統的Block動畫

但是蘋果文檔中更推薦使用《UIViewPropertyAnimator》來做動畫了

/** 用於對一個或多個視圖的改變的持續時間、延時、選項動畫完成時的操作 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 用於對一個或多個視圖的改變的持續時間、選項動畫完成時的操作,默認:delay = 0.0, options = 0 */
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 用於對一個或多個視圖的改變的持續時間內動畫完成時的操作,默認:delay = 0.0, options = 0, completion = NULL */
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0);
 
/** 使用與物理彈簧運動相對應的定時曲線執行視圖動畫 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
 
/** 爲指定的容器視圖創建轉換動畫 */
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 使用給定的參數在指定視圖之間創建轉換動畫 */
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview
 
/** 在一個或多個視圖上執行指定的系統提供的動畫,以及定義的可選並行動畫 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

首尾式動畫

通過上下文的方式對動畫進行配置

但是蘋果文檔中更推薦使用《UIViewPropertyAnimator》來做動畫了

/** 開始動畫 */
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;
/** 提交動畫 */
+ (void)commitAnimations;
 
/** 設置動畫代理, 默認nil */
+ (void)setAnimationDelegate:(nullable id)delegate;
/** 動畫將要開始時執行方法(必須要先設置動畫代理), 默認NULL */
+ (void)setAnimationWillStartSelector:(nullable SEL)selector;
/** 動畫已結束時執行方法(必須要先設置動畫代理), 默認NULL */
+ (void)setAnimationDidStopSelector:(nullable SEL)selector;
/** 設置動畫時長, 默認0.2秒 */
+ (void)setAnimationDuration:(NSTimeInterval)duration;
/** 動畫延遲執行時間, 默認0.0秒 */
+ (void)setAnimationDelay:(NSTimeInterval)delay;
/** 設置在動畫塊內部動畫屬性改變的開始時間, 默認now ([NSDate date]) */
+ (void)setAnimationStartDate:(NSDate *)startDate;
/** 設置動畫曲線, 默認UIViewAnimationCurveEaseInOut */
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;
/** 動畫的重複播放次數, 默認0 */
+ (void)setAnimationRepeatCount:(float)repeatCount;
/** 設置是否自定翻轉當前的動畫效果, 默認NO */
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;
/** 設置動畫從當前狀態開始播放, 默認NO */
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;
 
/** 在動畫塊中爲視圖設置過渡動畫 */
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
 
/** 設置是否激活動畫 */
+ (void)setAnimationsEnabled:(BOOL)enabled;

最後

本文主要是自己的學習與總結。如果文內存在紕漏、萬望留言斧正。如果願意補充以及不吝賜教小弟會更加感激。


參考資料

官方文檔--UIView
iOS UIView非常用方法及屬性詳解
詳解UICoordinateSpace和UIScreen在iOS 8上的座標問題
[iOS Animation]-CALayer 專用圖層
iOS冷門知識 -RightToLeft
iOS形變之CGAffineTransform
UIView的alpha、hidden和opaque屬性之間的關係和區別
iOS tintColor解析
ios開發 之 UIView詳解
使用 maskView 設計動畫
layoutMargins和preservesSuperviewLayoutMargins
iOS11適配-Safe Area
一個隨輸入文字寬度變化的自定義UITextField
HuggingPriority和CompressionResistance 一個例子教你理解
Masonry約束立即生效跟約束更新/繪製/layout
深入理解Auto Layout 第一彈
IOS-AutoresizesSubviewsAutoLayout的那些事兒
代碼添加constraint,設置translatesAutoresizingMaskIntoConstraints爲NO的原因
drawRect的繪製的使用(繪製文本字符、繪製圖片、繪製圖形)
內存惡鬼drawRect
深刻理解移動端優化之離屏渲染
iOS繪製和渲染
iOS的App實現狀態恢復

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章