每個應用都至少有一個 window 和一個 view。
一般在有外界顯示設備的時候才需要添加額外的 window
下面的代碼舉了一個例子,這裡假定對象實現了方法 externalWindow,externalWindow 存儲一個 window 的引用
- (void)configureExternalDisplayAndShowWithContent:(UIViewController*)rootVC { // Configure the content only if a second screen is available. if ([[UIScreen screens] count] > 1) { UIScreen* externalScreen = [[UIScreen screens] objectAtIndex:1]; CGRect screenBounds = externalScreen.bounds; // Configure the window self.externalWindow = [[UIWindow alloc] initWithFrame:screenBounds]; self.externalWindow.windowLevel = UIWindowLevelNormal; self.externalWindow.screen = externalScreen; // Install the root view controller self.externalWindow.rootViewController = rootVC; // Show the window, but do not make it key. self.externalWindow.hidden = NO; } else { // No external display available for configuration. } }
UIKit中的每個 view,底層都擁有一個 layer 對象,通常都是CALayer。大多數情況下都直接通過 UIView 操作;當需要更多控制的時候可以通過 layer 執行操作。
注意:bar button item 不是 view,所以不能直接訪問它的 layer。實際上 bar button item 是直接繼承自 NSObject,而 layer 是 UIView 中定義的,所以bar button item沒有。
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrrL0MS2r7utsuO74bbUIHZpZXe21M/zu+bWxrT6wuu1xMTayN29+NDQu7q05tbY08OjrNPIxuTKx9Ta09C2r7uttcTH6b/2z8KjrNbY08OxyNbY0MK0tL2o0ru49tDCtcTE2sjd18rUtM/7usTQobrctuChozwvcD4NCjxoMyBpZD0="211-視圖層級和子視圖管理">2.1.1 視圖層級和子視圖管理
如果子視圖是完全不透明的話,會遮蓋父視圖的內容。父視圖將子視圖存放在一個有序的數組中,所以後添加的(子視圖數組最後的)會在最上面顯示。
父視圖改變大小會引起子視圖的大小隨之變化,可以自定義這種變化。還有父視圖被隱藏、改變父視圖的透明度(alpha),或者對父視圖的坐標系統應用數學轉換等,都會影響到子視圖。
UIResponder 和它的子類可以響應事件,並處理事件,UIView就是繼承自 UIResponder。
點擊事件的響應是從上到下一層一層判斷的,如果最後沒有任何對象做出響應,通常就丟棄了。
第一次繪圖的時候系統保存一個繪圖內容的快拍(snapshot),如果之後再沒有對內容改動,那麼不會再調用繪圖代碼,都直接使用這個快拍;如果有了改動,就重新生成一個快拍,周而復始。
當視圖的內容或者外觀發生變化的時候可以使用 setNeedsDisplay 和 setNeedsDisplayInRect 方法進行重繪;注意如果改變了視圖的幾何形狀,這個方法就失效了。這兩個方法是等待當前 run loop 執行到最後的時候,再將剛才設置的所有重繪操作一次執行完成。
有關幾何形狀的變化,需要看下面的 Content Modes
UIView 的子類,通常重寫 drawRect: 方法,在這個方法裡面寫繪圖代碼。這個方法不需要自己調用。
當發生下列兩種情況時,內容模式就會應用:
改變視圖的 frame 或者 bounds 矩形的寬或高。 指定一個轉換,比如視圖的 transform 屬性的縮放比例@property(nonatomic) UIViewContentMode contentMode; // default is UIViewContentModeScaleToFill
typedef NS_ENUM(NSInteger, UIViewContentMode) { UIViewContentModeScaleToFill, UIViewContentModeScaleAspectFit, // contents scaled to fit with fixed aspect. remainder is transparent UIViewContentModeScaleAspectFill, // contents scaled to fill with fixed aspect. some portion of content may be clipped. UIViewContentModeRedraw, // redraw on bounds change (calls -setNeedsDisplay) UIViewContentModeCenter, // contents remain same size. positioned adjusted. UIViewContentModeTop, UIViewContentModeBottom, UIViewContentModeLeft, UIViewContentModeRight, UIViewContentModeTopLeft, UIViewContentModeTopRight, UIViewContentModeBottomLeft, UIViewContentModeBottomRight, };
UIViewContentModeRedraw 通常都不需要使用這個值,尤其在標准系統視圖中不要使用。
可以指定一個區域為可伸縮的,可以沿著一個軸或者兩個軸伸縮。下圖顯示了視圖自身顯示的失真
contentStretch 屬性用來指定可伸縮的區域。但是這個屬性iOS6之後被廢棄了,通常這個屬性都是用在 view 的背景 UIImage 對象,所以現在用 [UIImage resizableImageWithCapInsets:] 達到相同效果。
執行動畫需要做兩件事:
告訴UIKit 你想要執行動畫 改變屬性的值下面這些 UIView 對象的屬性都可以用作動畫:
frame 以動畫形式改變視圖的位置和大小 bounds 以動畫形式改變視圖的大小 center 以動畫形式改變視圖的位置 transform 旋轉或者伸縮視圖 alpha 改變視圖的透明度 backgroundColor 改變視圖的背景顏色比如通常可以用導航欄控制器控制兩個視圖的轉換動畫,這是提供的標准動畫,當覺得達不到想要效果,就可以自定義。
還可以直接使用 Core Animation layers創建動畫。
每個視圖和窗口都定義了自己的局部坐標系統。
下面3種情況,改變會有連鎖反應:
改變frame 屬性,bounds、center 屬性也會隨著改變 改變center 屬性,frame 的原點會改變 bounds 屬性的大小改變,frame 屬性也會改變利用仿射變換可以改變整個視圖的大小、位置或者方向。
transform 屬性可以修改變換方式,並且有動畫。
One point does not necessarily correspond to one pixel on the screen.
一個 point 並不一定和 一個像素相等,千萬不要有相等的假設。
考慮下面幾種情況:
1.用戶觸摸屏幕
2.硬件給UIKit框架報告觸摸事件
3.UIKit 框架把觸摸事件包裝為一個 UIEvent 對象,並且分配給適當的視圖。
4.視圖的 event-handling 代碼響應事件。比如,你的代碼可以:
改變視圖或者子視圖屬性(frame,bounds,alpha 等等) 調用 setNeedsLayout 方法來標記需要布局更新的視圖或者子視圖。 調用 setNeedsDisplay 或者 setNeedsDisplayInRect: 方法來標記需要重繪的視圖或它的子視圖。 通知 controller 有關一些數據塊的修改上面這都是由你來決定做哪些事情。
如果使用了手勢識別來處理事件,就不要重寫任何手勢識別的方法;同樣,如果視圖不包含任何子視圖或者它的大小沒有改變,不要重寫 layoutSubviews 方法;當你的視圖內容在運行時候需要改變,或者你正在使用比如Uikit或者Core Graphics的原生技術來進行繪圖時,才需要重寫 drawRect:
自定義視圖需要考慮性能問題;優化繪圖代碼之前,先測量性能,然後定個性能標准再優化。
視圖控制器提供的功能比如:協調視圖在屏幕上的顯示,協調視圖在屏幕上的移動,釋放內存,旋轉視圖等。
應該避免使用 UIViewContentModeRedraw。總是使用 setNeedsDisplay 或者 setNeedsDisplayInRect:
對於透明的渲染會增加性能的損耗。
滾動的時候非常損耗性能,所以可以考慮在滾動的時候暫時降低內容的渲染質量。滾動停止的時候再恢復。
永遠不要給系統控件自行添加視圖,這樣會導致很多錯誤發生。
這章節涉及內容不常用,用時再看 - Windows
窗口職責:
包含應用的可視化內容 在視圖觸摸事件和其他應用對象之間扮演遞送者 通常和視圖控制器協作,來適應方向的變化view 的職責:
布局和子視圖管理:
view 定義它對於父視圖的默認 resize 行為 視圖可以管理一系列子視圖 視圖可以根據需要調整子視圖的大小和位置 視圖可以將它的坐標系轉化為其他視圖或者窗口的坐標系繪制和動畫:
視圖在它的矩形區域繪制內容 view 的某些屬性可以以動畫的形式變換到新值事件處理:
視圖可以收到觸摸事件 視圖可以參與到響應鏈可以查看 Resource Programming Guide
CGRect viewRect = CGRectMake(0, 0, 100, 100); UIView* myView = [[UIView alloc] initWithFrame:viewRect];
view 的一些關鍵屬性的用法
tag 可以用一個整數值來標記特定的 view。默認這個屬性為 0。
找到標記的view,可以使用 viewWithTag: 方法。
addSubview: 方法直接添加到最上面。
- (void)removeFromSuperview; - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index; - (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2; - (void)addSubview:(UIView *)view; - (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview; - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview; - (void)bringSubviewToFront:(UIView *)view; - (void)sendSubviewToBack:(UIView *)view;
設置 hidden 屬性為 YES,或者將alpha 屬性設置為 0.0。隱藏view 之後就不能收到觸摸事件了。
如果要以動畫形式將view 從可視化到隱藏,必須使用 alpha 屬性。hidden 屬性不是可動畫的
兩種方式:
存儲相應視圖的指針,比如 view controller 擁有視圖的方式 給 view 的 tag 屬性賦值,但數字要獨一無二;然後用 viewWithTag: 方法拿到// 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;
UIView 定義了下面幾種方法在view 的局部坐標系中轉換坐標
convertPoint:fromView: convertRect:fromView: convertPoint:toView: convertRect:toView:類似,UIWindow也定義了幾個轉換方法:
convertPoint:fromWindow: convertRect:fromWindow: convertPoint:toWindow: convertRect:toWindow:
UIView 支持自動和手動布局
當 view 中有下面幾種事件發生的時候,布局需要改變:
view 的 bound 矩形大小發生變化 界面的方法發生變化,這通常會觸發根視圖的 bound 矩形變化 與 view layer相關的 Core Animation sublayer 的集合發生變化,並要求 layout 調用view 的 setNeedsLayout 或者 layoutIfNeeded 方法,讓應用強制布局 調用view的 layer對象的 setNeedsLayout 方法,讓應用強制布局設置父視圖 autoresizesSubviews 屬性來決定子視圖是否需要調整大小。如果這個屬性為 YES,每個子視圖的 autoresizingMask 屬性決定如何變化。
在自定義 view 中,如果自動布局行為沒有達到期望要求,可以實現 layoutSubviews ,可以下面幾件事:
調整任何當前子視圖的大小和位置 添加移除子視圖或者核心動畫層(Core Animation layers) 調用 setNeedsDisplay 或者 setNeedsDisplayInRect: 方法強制子視圖重繪有大片滾動區域的時候應用會經常手動布局子視圖。
寫布局代碼的時候測試代碼對於下面情況是否完善:
view方向改變的時候,確保布局對於所有支持方向都是正確的 確保你的代碼對於 status bar高度的變化有適當的響應。在 view controller 中:
view controller 在顯示 view 之前創建它們,可以從 nib 文件 load view 或者用代碼創建它們。但這些views 不在需要的時候,銷毀它們 當設備方向改變的時候, view controller 可能調整view 的大小和位置來匹配。對於新的方向,可能會隱藏一些view並且顯示另外一些view view controller 管理可編輯的內容,可能添加一下額外的按鈕,使得編輯更加的方便每個 view 的專屬 layer 屬性。
view 被創建之後 layer關聯view 的類型就不能改變了,每個 view 使用 layerClass 類方法指定 layer 對象的類,這個方法默認實現返回的是 CALayer 類,只能在子類中改變這個值,重寫方法,返回一個不同的值。
view 將自己設置為它的layer對象的代理;view 擁有它的layer。view 和 layer 之間的關系不能改變。
自定義 layer 對象可以是任何 CALayer 的實例,不被任何 view擁有。自定義 layer 不能接收時間,或者參與到響應鏈中,但是可以繪制自己,並且可以響應父視圖的大小變化或者根據核心動畫層規則響應。
給 view 添加自定義 layer 的實例代碼:
- (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)); // Release the layer, since it is retained by the view's layer [myLayer release]; }
自定義視圖需要注意下面這些情況:
為 view 定義適當的初始化方法:view 應該包含 initWithFrame: 方法。
- (id)initWithFrame:(CGRect)aRect { self = [super initWithFrame:aRect]; if (self) { // setup the initial properties of the view ... } return self; }
- (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); }
下面是UIView中可以用於動畫的一些屬性:
frame bounds center transform alpha backgroundColor contentStretch使用 Core Animation 可以對view 的layer 做下面類型的變化:
layer 大小和位置 當執行過渡時候,使用的中點 在 3D 空間中 layer或者 sublayer 的變化 從 layer 層級中添加或者移除 layer 對於其他同級layer 的Z軸順序 layer 的陰影 layer 的邊界(包含邊角是否是圓角) resize操作時候 layer 的部分伸縮 layer 的不透明度 在 layer邊界外面部分的裁剪行為 layer 的當前內容 layer 的光柵線下面有3個類方法:
animateWithDuration:animations: animateWithDuration:animations:completion: animateWithDuration:delay:options:animations:completion:這些方法都是開了新線程執行動畫,以防阻塞當前線程或者主線程。
[UIView animateWithDuration:1.0 animations:^{ firstView.alpha = 0.0; secondView.alpha = 1.0; }];
上面方法只是慢進慢出的單一動畫形式,想要復雜的,必須使用 animateWithDuration:delay:options:animations:completion: 方法,可以自定義下面動畫參數:
開始動畫前的延遲 動畫期間所使用的時序曲線類型 動畫應該重復的次數 動畫達到末尾的時候是否應該自動反轉 動畫進行的時候時候view 是否接收觸摸事件 當前動畫是否可以中斷正在進行的任何其他動畫,或者是等到其他都完成再開始
下面代碼設置了漸隱動畫,並且使用 completion handler,這是連接多個動畫的基本方式
- (IBAction)showHideView:(id)sender
{
// Fade out the view right away
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
thirdView.alpha = 0.0;
}
completion:^(BOOL finished){
// Wait one second and then fade in the view
[UIView animateWithDuration:1.0
delay: 1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
thirdView.alpha = 1.0;
}
completion:nil];
}];
}
5.2.2 使用 Begin/Commit 方法開始動畫
這是 iOS 3.2 之前使用的方法。。。
執行簡單的 begin/commit 動畫
[UIView beginAnimations:@"ToggleViews" context:nil];
[UIView setAnimationDuration:1.0];
// Make the animatable changes.
firstView.alpha = 0.0;
secondView.alpha = 1.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
配置動畫參數:
// This method begins the first animation.
- (IBAction)showHideView:(id)sender
{
[UIView beginAnimations:@"ShowHideView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(showHideDidStop:finished:context:)];
// Make the animatable changes.
thirdView.alpha = 0.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
}
// Called at the end of the preceding animation.
- (void)showHideDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
[UIView beginAnimations:@"ShowHideView2" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelay:1.0];
thirdView.alpha = 1.0;
[UIView commitAnimations];
}
5.2.3 配置動畫代理
如果想要在動畫開始前或者結束後立即執行代碼,就需要將代理對象和 start or stop selector,與 begin/commit 動畫塊聯結起來。使用UIView的類方法 setAnimationDelegate: 設置代理對象。使用 setAnimationWillStartSelector: 和 setAnimationDidStopSelector: 類方法設置開始和結束的 selector。
類似下面的代碼:
- (void)animationWillStart:(NSString *)animationID context:(void *)context;
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;
在基於 block 的動畫方法中不需要使用上面這種形式。直接在動畫block之前放置想要在動畫前執行的代碼,在 completion handler 裡面放置想要在動畫結束後執行的代碼。
5.2.4 嵌套動畫 block
被嵌套的動畫與父動畫同一時間開始,但可以以自己的配置選項運行。默認被嵌套的動畫繼承了父動畫的持續時間和動畫曲線。
有不同配置的嵌套動畫
[UIView animateWithDuration:1.0
delay: 1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
aView.alpha = 0.0;
// Create a nested animation that has a different
// duration, timing curve, and configuration.
[UIView animateWithDuration:0.2
delay:0.0
options: UIViewAnimationOptionOverrideInheritedCurve |
UIViewAnimationOptionCurveLinear |
UIViewAnimationOptionOverrideInheritedDuration |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAutoreverse
animations:^{
[UIView setAnimationRepeatCount:2.5];
anotherView.alpha = 0.0;
}
completion:nil];
}
completion:nil];
5.2.5 實現動畫的逆行
5.3 在view 之間創建動畫過渡
不要把 view transition 和 view controller 的變換搞混,view transition 只是影響 view 層級
5.3.1 改變視圖的子視圖
transitionWithView:duration:options:animations:completion: 方法。UIViewAnimationOptionAllowAnimatedContent 設置選項。
將空的文本視圖與現有的進行交換
- (IBAction)displayNewPage:(id)sender
{
[UIView transitionWithView:self.view
duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
animations:^{
currentTextView.hidden = YES;
swapTextView.hidden = NO;
}
completion:^(BOOL finished){
// Save the old text and then swap the views.
[self saveNotes:temp];
UIView* temp = currentTextView;
currentTextView = swapTextView;
swapTextView = temp;
}];
}
5.3.2 替換view
只是交換兩個 view,不是 view controllers。
一個 view controller 中兩個 view 之間的開關
- (IBAction)toggleMainViews:(id)sender {
[UIView transitionFromView:(displayingPrimary ? primaryView : secondaryView)
toView:(displayingPrimary ? secondaryView : primaryView)
duration:1.0
options:(displayingPrimary ? UIViewAnimationOptionTransitionFlipFromRight :
UIViewAnimationOptionTransitionFlipFromLeft)
completion:^(BOOL finished) {
if (finished) {
displayingPrimary = !displayingPrimary;
}
}];
}
5.4 將多個動畫連接在一起
5.5 Animating View and Layer Changes Together
下面代碼顯示了同時修改view 和 自定義 layer 的動畫。例子中的 view 包含了一個自定義 CALayer ,在view 的 bounds 中央。當順時針旋轉 layer 時,逆時針旋轉 view。
Mixing view and layer animations
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
// Animate the first half of the view rotation.
CGAffineTransform xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-180));
backingView.transform = xform;
// Rotate the embedded CALayer in the opposite direction.
CABasicAnimation* layerAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
layerAnimation.duration = 2.0;
layerAnimation.beginTime = 0; //CACurrentMediaTime() + 1;
layerAnimation.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
layerAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
layerAnimation.fromValue = [NSNumber numberWithFloat:0.0];
layerAnimation.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(360.0)];
layerAnimation.byValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(180.0)];
[manLayer addAnimation:layerAnimation forKey:@"layerAnimation"];
}
completion:^(BOOL finished){
// Now do the second half of the view rotation.
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
CGAffineTransform xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-359));
backingView.transform = xform;
}
completion:^(BOOL finished){
backingView.transform = CGAffineTransformIdentity;
}];
}];
蘋果官方文檔地址:View Programming Guide for iOS