系統化學習,知其然,知其所以然
一、創建和配置View對象(Creating and Configuring View Objects)
有兩種方式可以創建View對象:編程方式 和 Interface Builder
1.1 創建
方式1:Interface Builder
創建VIew最簡單的方式是使用 Interface Builder,可以達到所見即所得效果。您在設計時看到的是運行時獲得的內容。將活動對象保存在一個nib文件中,這是一個資源文件,用于保存對象的狀態和配置。
在視圖控制器中使用nib文件時,只需使用nib文件信息初始化視圖控制器即可。視圖控制器在適當的時候處理視圖的加載和卸載。但是,如果您的nib文件未與視圖控制器相關聯,則可以使用NSBundle或UINib對象手動加載nib文件內容,該對象使用nib文件中的數據重新構建視圖對象。
方式2:Programmatically
如果以編程方式創建視圖,則可以使用標準 allocation/initialization 模式來執行此操作。 視圖的默認初始化方法是 initWithFrame:方法,該方法設置視圖相對于(即將建立的)父視圖的初始大小和位置。 例如,要創建一個新的泛型UIView對象,可以使用類似于以下的代碼:
CGRect viewRect = CGRectMake(0,0,100,100);
UIView * myView = [[UIView alloc] initWithFrame:viewRect];
注意:雖然所有的視圖都支持initWithFrame:方法,但是每個View也可能有一個建議首選的初始化方法。 有關任何自定義初始化方法的信息,請參閱該類的參考文檔。
1.2 設置屬性 Setting the Properties of a View
UIView有幾個聲明的屬性來控制視圖的外觀和行為。 這些屬性用于操縱視圖的大小和位置,視圖的透明度,背景顏色和渲染行為。 所有這些屬性都具有適當的默認值,您可以根據需要稍后進行更改。 可以通過 Programmatically 和 Interface Builder 兩張途徑修改。詳情請查看API文檔。
1.3 標記視圖 Tagging Views for Future Identification
UIView包含一個tag屬性,可以使用一個整數值來唯一標記一個View,并在運行時執行對這些視圖的搜索。 (基于標記的搜索比自己迭代視圖層次更快。)tag屬性的默認值為0。
使用UIView的 viewWithTag: 方法搜索視圖。 此方法執行View及其子視圖的深度優先搜索。不會向上或者橫行搜索,只向下搜索,所以從root view 開始可以得到更多內容。
二、創建和管理視圖層次結構 Creating and Managing a View Hierarchy
2.1 添加和刪除子視圖 Adding and Removing Subviews
Interface Builder 是構建視圖層次結構最方便的方式。可以用圖形方式組裝視圖,查看視圖之間的關系,并確切了解在運行時將如何顯示這些視圖。使用Interface Builder時,將結果視圖層次結構保存在一個nib文件中,在運行時加載,因為需要相應的視圖。
以編程方式創建視圖,請創建并初始化它們,然后使用以下方法將它們排列為層次結構:
//添加
- (void)addSubview:(UIView *)view;
//插入
- (void)insertSubview:(UIView *)view
atIndex:(NSInteger)index;
- (void)insertSubview:(UIView *)view
aboveSubview:(UIView *)siblingSubview;
- (void)insertSubview:(UIView *)view
belowSubview:(UIView *)siblingSubview;
//跳轉順序
- (void)bringSubviewToFront:(UIView *)view;
- (void)sendSubviewToBack:(UIView *)view;
- (void)exchangeSubviewAtIndex:(NSInteger)index1
withSubviewAtIndex:(NSInteger)index2;
//移除
- (void)removeFromSuperview;
在視圖控制器的 loadView 或 viewDidLoad 方法可以添加子視圖到視圖層次結構。
- 如果以編程方式構建視圖,則將視圖創建代碼放置在視圖控制器的 loadView 方法中。
- 無論是以編程方式創建視圖還是從nib文件加載視圖,都可以在 viewDidLoad 方法中包含其他視圖配置代碼。
當將子視圖添加到另一個視圖時,UIKit通知更改的父視圖和子視圖。 如果實現自定義視圖,則可以通過覆蓋以下方法中的一個或者多個來截獲這些通知。
//父視圖即將變化
- (void)willMoveToSuperview:(UIView *)newSuperview;
//父視圖已經變化
- (void)didMoveToSuperview;
//調用view的窗口即將變化
- (void)willMoveToWindow:(UIWindow *)newWindow;
//調用view的窗口變化
- (void)didMoveToWindow;
//一個子視圖即將移除
- (void)willRemoveSubview:(UIView *)subview;
//一個子視圖已添加
- (void)didAddSubview:(UIView *)subview;
2.2 隱藏視圖 (Hiding Views)
隱藏視圖有兩種方式
- 設置 hidden = YES ;
- 設置 alpha = 0 ;
但是有以下地方需要注意
- 隱藏視圖仍然參與自動布局,如果還需要顯示話,隱藏比移除效果更好。
- 隱藏視圖不會自動退出 first responder ,需要手動結束;
- hidden不是可以做動畫屬性,使用alpha 替代 ;
2.3 訪問視圖層次結構中的視圖(Locating Views in a View Hierarchy)
有兩種方式可以訪問
- 保持指針
- 使用tag屬性
2.4 Translating, Scaling, and Rotating Views
可以使用 UIView transform 屬性進行translate、 scale、 rotate 操作。transform 屬性包含了一個 CGAffineTransform 結構體,默認為 identity transform ,不會更改視圖的外觀。
例如
// M_PI/4.0 is one quarter of a half circle, or 45 degrees.
CGAffineTransform xform = CGAffineTransformMakeRotation(M_PI/4.0);
self.view.transform = xform;
[圖片上傳失敗...(image-db10b0-1511255073202)]
注意點
- 仿射變換添加順序很重要,會導致不同結果
- 仿射變換的中心點是視圖的 Center 。
- 更多詳情
2.5 坐標轉換 (Converting Coordinates in the View Hierarchy)
- UIView通過以下方法轉換坐標
- (CGPoint)convertPoint:(CGPoint)point
fromView:(UIView *)view;
- (CGPoint)convertPoint:(CGPoint)point
toView:(UIView *)view;
- (CGRect)convertRect:(CGRect)rect
fromView:(UIView *)view;
- (CGRect)convertRect:(CGRect)rect
toView:(UIView *)view;
- UIWindow通過以下方法轉換坐標
- (CGPoint)convertPoint:(CGPoint)point
fromWindow:(UIWindow *)window;
- (CGRect)convertRect:(CGRect)rect
fromWindow:(UIWindow *)window;
- (CGPoint)convertPoint:(CGPoint)point
toWindow:(UIWindow *)window;
- (CGRect)convertRect:(CGRect)rect
toWindow:(UIWindow *)window;
三、在運行時調整視圖的大小和位置(Adjusting the Size and Position of Views at Runtime)
每當視圖的大小發生變化時,其子視圖的大小和位置都必須相應地改變。 UIView支持自動布局和手動布局。
- 通過自動布局,您可以設置每個視圖在其父視圖調整大小時應遵循的規則,然后完全忽略調整大小的操作。
- 通過手動布局,您可以根據需要手動調整視圖的大小和位置。
3.1 觸發布局變化條件(Being Prepared for Layout Changes)
在視圖中發生以下任何事件時,可能會發生布局更改:
視圖的bounds屬性發生變化
設備方向更改例如橫屏,通常會觸發rootview的bounds發生變化
view的layer發生更改,并且需要布局。
調用視圖的setNeedsLayout或layoutIfNeeded方法來強制執行布局
通過調用視圖底層對象的setNeedsLayout方法來強制執行布局
3.2 使用自動調整規則自動處理布局更改(Handling Layout Changes Automatically Using Autoresizing Rules)
當您更改視圖的大小時,通常需要更改嵌入子視圖的位置和大小,以適應其父視圖的新大小。
superview 的 autoresizesSubviews 屬性確定子視圖是否調整大小。
如果此屬性設置為YES,則視圖使用每個子視圖的 autoresizingMask 屬性來確定如何調整和定位該子視圖。
對任何子視圖的大小更改會觸發嵌入式子視圖的類似布局調整。
對于視圖層次結構中的每個視圖,將該視圖的autoresizingMask屬性設置為適當的值是處理自動布局更改的重要部分。表3-2列出了可應用于給定視圖的自動調整選項,并描述了在布局操作過程中的效果。可以使用 OR 運算符組合或者相加。如果使用Interface Builder來組裝視圖,則可以使用“自動調整大小”檢查器來設置這些屬性。
//默認值,不會自動調整大小
UIViewAutoresizingNone
//父視圖的高度改變時改變高度
UIViewAutoresizingFlexibleHeight
//父視圖的寬度改變時改變寬度
UIViewAutoresizingFlexibleWidth
//視圖左邊緣和父視圖左邊緣之間的距離根據需要增長或縮小。如果不包含此常數,則視圖的左邊距離超視圖的左邊緣保持固定的距離。
UIViewAutoresizingFlexibleLeftMargin
//視圖右邊緣和父視圖右邊緣之間的距離根據需要增長或縮小。如果不包含此常數,則視圖的右邊距離超視圖的右邊緣保持固定的距離。
UIViewAutoresizingFlexibleRightMargin
//視圖下邊緣和父視圖下邊緣之間的距離根據需要增長或縮小。如果不包含此常數,則視圖的下邊距離超視圖的下邊緣保持固定的距離。
UIViewAutoresizingFlexibleBottomMargin
//視圖上邊緣和父視圖上邊緣之間的距離根據需要增長或縮小。如果不包含此常數,則視圖的上邊距離超視圖的上邊緣保持固定的距離。
UIViewAutoresizingFlexibleTopMargin
[圖片上傳失敗...(image-9dbd73-1511255073202)]
其中設置常量的地方會自動調整,否則為固定值。配置自動調整規則的最簡單方法是使用Interface Builder的“大小”檢查器中的“自動調整”控件。上圖中靈活的寬度和高度常數與“自動調整”控件圖中的寬度和大小指示器具有相同的行為。但是,指示效果是相反的。在界面構建器中,邊緣指示符的存在意味著邊距具有固定大小,并且缺少指示符意味著邊距具有靈活的大小。幸運的是,Interface Builder提供了一個動畫來展示自動修改行為對你的視圖的影響。
3.3 手動調整視圖的布局(Tweaking the Layout of Your Views Manually)
只要視圖的大小發生變化,UIKit就會應用該視圖的子視圖的自動調整行為,然后調用視圖的 layoutSubviews 方法以使其進行手動更改。 當自動調整沒有產生所需的結果時可以在自定義視圖中實現 layoutSubviews 方法。 此方法的實現可以執行以下任何操作:
調整任何直接子視圖的大小和位置。
添加或刪除子視圖或核心動畫層。
通過調用setNeedsDisplay或setNeedsDisplayInRect:方法強制子視圖重繪。
經常手動布置子視圖的一個地方是在實現大的可滾動區域時。由于對其可滾動內容擁有單個大視圖是不切實際的,因此應用程序通常會實現一個根視圖,其中包含許多較小的視圖。每個圖塊代表可滾動內容的一部分。當滾動事件發生時,根視圖調用其setNeedsLayout方法來啟動布局更改。其layoutSubviews方法然后根據發生的滾動量重新定位平鋪視圖。當tile從視圖的可見區域滾出時,layoutSubviews方法將tile移動到傳入邊緣,替換進程中的內容。
編寫布局代碼時,請務必以下列方式測試代碼:
- 更改視圖的方向以確保布局在所有支持的接口方向上正確。
- 確保你的代碼正確響應狀態欄高度的變化。當打電話時,狀態欄高度會增加,當用戶結束通話時,狀態欄的大小會減小。
四、運行時修改視圖(Modifying Views at Runtime
由于應用程序從用戶接收輸入,他們調整其用戶界面以響應該輸入。 應用程序可能會通過重新排列視圖,更改其大小或位置,隱藏或顯示視圖或加載全新的視圖來修改視圖。 在iOS應用程序中,有幾種地方需要和方法可以執行這些操作:
-
在 view controller 中:
- 1 視圖控制器必須在顯示之前創建其視圖。它可以從一個nib文件加載視圖或以編程方式創建它們。當這些視圖不再需要時,就把它們處理掉。
- 2 當設備改變方向時,視圖控制器可能會調整視圖的大小和位置以匹配。作為調整新方向的一部分,可能會隱藏一些視圖,并顯示其他視圖。
- 3 當視圖控制器管理可編輯的內容時,它可能會調整其視圖層次結構。例如,它可能會添加額外的按鈕和其他控件來方便編輯其內容的各個方面,這可能還需要調整任何現有的視圖以適應現有的頁面布局。
-
在 animation blocks 中:
- 1 當您想要在用戶界面的不同視圖之間切換時,可以隱藏一些視圖并在動畫塊中顯示其他視圖
- 2 實現特殊效果時,可以使用動畫塊來修改視圖的各種屬性。例如,要動畫改變視圖的大小,你可以改變它的frame的大小
-
其他方法:
五、與圖層進行交互(Interacting with Core Animation Layers)
每個視圖對象都有一個專用的圖層,用于管理屏幕上視圖內容的顯示和動畫。 雖然您可以使用視圖對象做很多事情,但您也可以根據需要直接使用相應的圖層對象。 視圖的圖層對象存儲在視圖的layer屬性中。
5.1 更改與視圖關聯的圖層類(Changing the Layer Class Associated with a View)
View的layer在其創建完成后無法更改。原因在于
- view在初始化之前先調用layerClass方法來獲取layer對象;
- 然后layer.delegate = view;
- view持有layer
- view不能作為其他layer的delegate
- 更改視圖的所有權或委托代理關系會導致繪圖問題和應用程序崩潰
更改View默認layer的唯一方法是創建子類,重寫該方法并返回不同的值。例如,
+ (Class)layerClass
{
return [CATiledLayer class];
}
5.2 Embedding Layer Objects in a View
如果喜歡使用layer,可以使用自定義layer添加到View中構建視層級結構。自定義圖層負責渲染內容和調整大小位置,不響應事件。
例如
- (void)viewDidLoad {
[super viewDidLoad];
// Create the layer.
CALayer* myLayer = [[CALayer alloc] init];
// Set the contents of the layer to a fixed image. And set
// the size of the layer to match the image size.
UIImage layerContents = [[UIImage imageNamed:@"myImage"] retain];
CGSize imageSize = layerContents.size;
myLayer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
myLayer = layerContents.CGImage;
// Add the layer to the view.
CALayer* viewLayer = self.view.layer;
[viewLayer addSublayer:myLayer];
// Center the layer in the view.
CGRect viewBounds = backingView.bounds;
myLayer.position = CGPointMake(CGRectGetMidX(viewBounds), CGRectGetMidY(viewBounds));
}
六、自定義視圖(Defining a Custom View)
6.1 重點事項 Checklist for Implementing a Custom View
-
1 定義合適的初始化方法
1 代碼 重寫
- (instancetype)initWithFrame:(CGRect)frame;
方法或者定義新的方法1 nib文件加載的視圖,重寫
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
方法;
2 實現
- (void)dealloc;
方法來清理自定義數據。3 要處理任何自定義繪圖,請覆蓋
- (void)drawRect:(CGRect)rect;
方法并在那里繪制繪圖4 設置視圖的autoresizingMask屬性以定義其自動布局行為。
-
5 如果View管理一個或多個子視圖,請執行以下操作:
- 1 在視圖的初始化序列中創建這些子視圖。
- 1 在創建時設置每個子視圖的autoresizingMask屬性。
- 1 如果子視圖需要自定義布局,請覆蓋layoutSubviews方法以實現您的布局代碼。
-
6 要處理基于觸摸的事件,請執行以下操作:
- 1 通過使用
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
方法添加手勢到視圖 - 1 自己處理觸摸事件,重新以下方法
- 1 通過使用
- (void)touchesBegan:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- 7 如果打印 view 版本信息,請實現
- (void)drawRect:(CGRect)rect forViewPrintFormatter:(UIViewPrintFormatter *)formatter;
方法。
除了可以重新的方法外,還有很多通過API直接設置的屬性來控制view顯示效果。
6.2 初始化自定義視圖(Initializing Your Custom View)
- 代碼 重寫
- (instancetype)initWithFrame:(CGRect)frame;
方法或者定義新的方法。
例如
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
...
}
return self;
}
- nib文件加載的視圖,重寫
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
方法;可以通過實現- (void)awakeFromNib;
添加額外的初始化工作。
6.3 實現繪圖代碼(Implementing Your Drawing Code)
一般來說,首選系統提供的標準視圖或者組合它們來呈現您的內容,那么這是首選。然后選擇自定義繪圖,對于需要進行自定義繪圖的視圖,您需要重寫 - (void)drawRect:(CGRect)rect;
方法并在那里進行繪制。
在調用視圖的drawRect:
方法之前,UIKit為視圖配置基本的繪圖環境。具體來說,它創建一個圖形上下文,并調整坐標系、剪輯區域以匹配視圖的坐標系和可見邊界。在調用drawRect:
方法時,可以使用原生繪圖技術(如UIKit和Core Graphics)開始繪制內容。可以使用UIGraphicsGetCurrentContext
函數獲取指向當前圖形上下文的指針。
drawRect:方法的實現應該完成一件事情:繪制你的內容。
此方法不是要更新數據或執行任何與繪圖無關的任務的地方。
它應該配置繪圖環境,繪制您的內容,并盡快退出。如果drawRect:方法可能會被頻繁地調用,那么應該盡可能地優化繪圖代碼,并在每次調用方法時盡可能少地繪制。
重要提示:當前的圖形上下文僅在對視圖的drawRect:方法進行一次調用期間才有效。 UIKit可能為這個方法的每個后續調用創建一個不同的圖形上下文,所以你不應該嘗試緩存對象并在以后使用它。
示例代碼
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
// Set the line width to 10 and inset the rectangle by
// 5 pixels on all sides to compensate for the wider line.
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5, 5);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
性能優化的2條建議
- view.opaque = YES;
- view.clearsContextBeforeDrawing = NO;
6.4 事件響應(Responding to Events)
- 1 通過添加手勢來處理事件。使用
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
方法添加手勢到視圖 - 2 自己處理觸摸事件,重新以下方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event;
- 3 multipleTouchEnabled 屬性控制多點觸控
- 4 響應事件開關有兩種方式
- userInteractionEnabled 屬性控制是否響應事件
- 通過一對方法也可以控制
//此二者方法通常在動畫開始結束時調用。動畫期間是不響應觸摸事件。
- (void)beginIgnoringInteractionEvents;
- (void)endIgnoringInteractionEvents;
- 5 通過重寫以下方法來判斷觸摸事件是否發生在視圖內
- (BOOL)pointInside:(CGPoint)point
withEvent:(UIEvent *)event;
- (UIView *)hitTest:(CGPoint)point
withEvent:(UIEvent *)event;
6.5 清理(Cleaning Up After Your View)
重寫- (void)dealloc;
方法來清理內容,例如
- (void)dealloc {
// Release a retained UIColor object
[color release];
// Call the inherited implementation
[super dealloc];
}
但是在ARC模式下幾乎不用關注這部分工作。