每个应用都至少有一个 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没有。
核心动画层会对 view对象绘制代码的内容进行缓存重用,尤其是在有动画的情况下,重用比重新创建一个新的内容资源消耗小很多。
如果子视图是完全不透明的话,会遮盖父视图的内容。父视图将子视图存放在一个有序的数组中,所以后添加的(子视图数组最后的)会在最上面显示。
父视图改变大小会引起子视图的大小随之变化,可以自定义这种变化。还有父视图被隐藏、改变父视图的透明度(alpha),或者对父视图的坐标系统应用数学转换等,都会影响到子视图。
UIResponder 和它的子类可以响应事件,并处理事件,UIView就是继承自 UIResponder。
点击事件的响应是从上到下一层一层判断的,如果最后没有任何对象做出响应,通常就丢弃了。
第一次绘图的时候系统保存一个绘图内容的快拍(snapshot),如果之后再没有对内容改动,那么不会再调用绘图代码,都直接使用这个快拍;如果有了改动,就重新生成一个快拍,周而复始。
当视图的内容或者外观发生变化的时候可以使用 setNeedsDisplay 和 setNeedsDisplayInRect 方法进行重绘;注意如果改变了视图的几何形状,这个方法就失效了。这两个方法是等待当前 run loop 执行到最后的时候,再将刚才设置的所有重绘操作一次执行完成。
有关几何形状的变化,需要看下面的 Content Modes
UIView 的子类,通常重写 drawRect: 方法,在这个方法里面写绘图代码。这个方法不需要自己调用。
当发生下列两种情况时,内容模式就会应用:
@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:] 达到相同效果。
执行动画需要做两件事:
下面这些 UIView 对象的属性都可以用作动画:
比如通常可以用导航栏控制器控制两个视图的转换动画,这是提供的标准动画,当觉得达不到想要效果,就可以自定义。
还可以直接使用 Core Animation layers创建动画。
每个视图和窗口都定义了自己的局部坐标系统。
下面3种情况,改变会有连锁反应:
利用仿射变换可以改变整个视图的大小、位置或者方向。
transform 属性可以修改变换方式,并且有动画。
One point does not necessarily correspond to one pixel on the screen.
一个 point 并不一定和 一个像素相等,千万不要有相等的假设。
考虑下面几种情况:
1.用户触摸屏幕
2.硬件给UIKit框架报告触摸事件
3.UIKit 框架把触摸事件包装为一个 UIEvent 对象,并且分配给适当的视图。
4.视图的 event-handling 代码响应事件。比如,你的代码可以:
上面这都是由你来决定做哪些事情。
如果使用了手势识别来处理事件,就不要重写任何手势识别的方法;同样,如果视图不包含任何子视图或者它的大小没有改变,不要重写 layoutSubviews 方法;当你的视图内容在运行时候需要改变,或者你正在使用比如Uikit或者Core Graphics的原生技术来进行绘图时,才需要重写 drawRect:
自定义视图需要考虑性能问题;优化绘图代码之前,先测量性能,然后定个性能标准再优化。
视图控制器提供的功能比如:协调视图在屏幕上的显示,协调视图在屏幕上的移动,释放内存,旋转视图等。
应该避免使用 UIViewContentModeRedraw。总是使用 setNeedsDisplay 或者 setNeedsDisplayInRect:
对于透明的渲染会增加性能的损耗。
滚动的时候非常损耗性能,所以可以考虑在滚动的时候暂时降低内容的渲染质量。滚动停止的时候再恢复。
永远不要给系统控件自行添加视图,这样会导致很多错误发生。
这章节涉及内容不常用,用时再看 - Windows
窗口职责:
view 的职责:
布局和子视图管理:
绘制和动画:
事件处理:
可以查看 Resource Programming Guide
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
view 的一些关键属性的用法
属性 | 用法 |
---|---|
aplha, hidden, opaque | 这些属性影响view 的透明度,alpha 和 hidden 属性直接改变 view 透明度;opaque 属性告诉系统是否应该混合视图的显示。设置为YES 可以提升性能。 |
bounds, frame, center, transform | center 和 frame 属性都和父视图相关,而bounds属性在自己的坐标系中定义了可视化内容区域。transform 属性常用语动画或者用复杂的方式移动 view。 |
autoresizingMask, autoresizesSubviews | 这些属性影响视图和子视图的自动 resieze 行为。autoresizingMask 属性控制视图在父视图中如何响应变化。autoresizesSubviews 属性控制是否当前视图的子视图要完全被 resize。 |
contentMode, contentStretch, contentScaleFactor | 这些属性影响 view 里面内容的渲染行为。 |
gestureRecognizers, userInteractionEnabled, multipleTouchEnabled, exclusiveTouch | 这些属性影响 view 如何处理触摸事件。 |
backgroundColor, subviews, drawRect:方法, layer, (layerClass 方法) | 这些属性和方法帮你管理 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 属性不是可动画的
两种方式:
// 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 的局部坐标系中转换坐标
类似,UIWindow也定义了几个转换方法:
UIView 支持自动和手动布局
当 view 中有下面几种事件发生的时候,布局需要改变:
设置父视图 autoresizesSubviews 属性来决定子视图是否需要调整大小。如果这个属性为 YES,每个子视图的 autoresizingMask 属性决定如何变化。
Autoresizing mask | 描述 |
---|---|
UIViewAutoresizingNone | 默认值,view不会自动调整大小 |
UIViewAutoresizingFlexibleHeight | 当父视图的高变化的时候,视图的高也会变化。如果没有包含这个常量,视图的高不变 |
UIViewAutoresizingFlexibleWidth | 当父视图的宽变化的时候,视图的宽也会变化。如果没有包含这个常量,视图的宽不变 |
UIViewAutoresizingFlexibleLeftMargin | 视图左边界与父视图左边界的距离会按需要增大或减小。如果没有包含这个常量,将维持固定的距离 |
UIViewAutoresizingFlexibleRightMargin | 视图右边界与父视图右边界的距离会按需要增大或减小。如果没有包含这个常量,将维持固定的距离 |
UIViewAutoresizingFlexibleBottomMargin | 视图下边界与父视图下边界的距离会按需要增大或减小。如果没有包含这个常量,将维持固定的距离 |
UIViewAutoresizingFlexibleTopMargin | 视图上边界与父视图上边界的距离会按需要增大或减小。如果没有包含这个常量,将维持固定的距离 |
在自定义 view 中,如果自动布局行为没有达到期望要求,可以实现 layoutSubviews ,可以下面几件事:
有大片滚动区域的时候应用会经常手动布局子视图。
写布局代码的时候测试代码对于下面情况是否完善:
在 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 应该包含 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中可以用于动画的一些属性:
使用 Core Animation 可以对view 的layer 做下面类型的变化:
下面有3个类方法:
这些方法都是开了新线程执行动画,以防阻塞当前线程或者主线程。
[UIView animateWithDuration:1.0 animations:^{
firstView.alpha = 0.0;
secondView.alpha = 1.0;
}];
上面方法只是慢进慢出的单一动画形式,想要复杂的,必须使用 animateWithDuration:delay:options:animations:completion:
方法,可以自定义下面动画参数:
下面代码设置了渐隐动画,并且使用 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];
}];
}
这是 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];
}
如果想要在动画开始前或者结束后立即执行代码,就需要将代理对象和 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 里面放置想要在动画结束后执行的代码。
被嵌套的动画与父动画同一时间开始,但可以以自己的配置选项运行。默认被嵌套的动画继承了父动画的持续时间和动画曲线。
有不同配置的嵌套动画
[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];
不要把 view transition 和 view controller 的变换搞混,view transition 只是影响 view 层级
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;
}];
}
只是交换两个 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;
}
}];
}
下面代码显示了同时修改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
新博客文章地址:View Programming Guide for iOS
View Programming Guide for iOS 视图编程指南 - iOS
原文:http://blog.csdn.net/zyq522376829/article/details/52437919