SlideShare uma empresa Scribd logo
1 de 80
Baixar para ler offline
⼦子类化 UIView 和 UIScrollView




范圣刚,princetoad@gmail.com,www.tfan.org
什么是 view?
• 我们在前⾯面创建过 UIButton,UILabel等,但是究
 竟什么是 view?
 • ⼀一个 view 是 UIView 或者它的某⼀一⼦子类的实例
 • view 知道如何把它⾃自⼰己绘制到应⽤用程序窗⼝口上(⼀一个
  UIWindow 的实例)

 • view 存在于 view 的层次结构中 view hierarchy。view
  hierarchy 的根是应⽤用程序窗⼝口
 • view 要处理事件,⽐比如触控事件
⾃自定义 UIView ⼦子类
• 使⽤用同⼼心圆填满屏幕
• 绘制⽂文本
• 启⽤用滚动和放⼤大缩⼩小
Hypnosister(催眠应⽤用)
• 创建⼀一个新的 iOS 项⺫⽬目,空项⺫⽬目
• 产品名:Hypnosister
• 类前缀:Hypnosister
• 勾选 “Use Automatic Reference Counting”
视图和视图层次结构
• 视图构成了应⽤用的⽤用户界⾯面
• 每个视图维护⼀一个⽤用于表⽰示它的图像,例如
 UIButton 的图像就是⼀一个在中间带有标题的圆⾓角
 矩形;⼀一个 UILabel 的图像就是简单的⽂文本

• 当关于 view 的⼀一些东⻄西变化时,例如 UILabel 的
 text 属性或 UIButton 的 title,view 的图像被重绘
 以便这些变更能够在屏幕上变得可⻅见
UIWindow
      • UIWindow 是 UIView 的⼦子类
      • 每个应⽤用程序恰好有⼀一个 UIWindow 的实例作为
        在应⽤用中所有视图的容器,当应⽤用启动的时候窗
        ⼝口被创建
      • HypnosisterAppDelegate.m 中的
        application:didFinishLaunchingWithOptions: ⽅方
        法中创建 UIWindow 对象并发送消息
        makeKeyAndVisible
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
subview
• window 被放到屏幕上以后,就可以在它上⾯面增加
 其他 view,这样的 view 就叫做 window 的
 subview
• 作为 window 的 subview 的 view 也可以具有
 subviews,结果就形成了⼀一个视图对象的层次结
 构
• 当且仅当⼀一个视图被添加到这个层次结构以后才
 会在屏幕上显⽰示,⽆无论它是作为 window 的⼀一个
 subview,还是作为另外⼀一个已经添加到 window
 的 view 的 subview
• 因此,window 是 view hierarchy 的 root
redrawn(重绘)
• 当屏幕被重绘的时候,⾸首先是 window 的图像被
 绘制到屏幕上
• 然后,window 的所有 subview 把它们⾃自⼰己的图像
 绘制到屏幕上
• 接着,subviews 的 subviews 再绘制它们的图像,
 依此类推
绘制 view hierarchy 到屏幕
⽤用户界⾯面⽣生成
• 创建每⼀一个 view 的图像并且把每⼀一个 view 加到
 view hierarchy
• 类似 UIButton,MKMapView 以及 UITextField 这些
 (Apple本⾝身提供的)已经知道它们的图像看起来
 是什么样⼦子
• 另外⼀一种情况是我们需要创建⼀一个⾃自定义的视图
 对象并且编写代码来创建它的图像
创建⼀一个⾃自定义视图
• ⼦子类化 UIView,并且⾃自定义⼦子类的图像
 • 创建⼀一个类 HypnosisView, 从 UIView 继承
 • 在 HypnosisterAppDelegate.m 中引⼊入 HypnosisView.h
  头⽂文件

 • 在 application:didFinishLaunchingWithOptions: 中创建
  HypnosisView 的实例并把它作为 window 的 subview
  添加到 view hierarchy 中
创建 HypnosisView 的实例并添
    加到UIWindow
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]
bounds]];

    CGRect viewFrame = CGRectMake(160, 240, 100, 150);

    HypnosisView *view = [[HypnosisView alloc] initWithFrame:viewFrame];
    [view setBackgroundColor:[UIColor redColor]];

    [[self window] addSubview:view];

    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
subview 和 superview
• 红⾊色的 HypnosisView 实例
 在⽩白⾊色的 UIWindow 之上绘
 制;HypnosisView 实例是
 UIWindow 的 subview

• 把⼀一个 view 作为另外⼀一个
 view 的 subview 添加时,反
 向关系同时也⾃自动建⽴立了,
 HypnosisView 的 superview
 是 UIWindow

• XIB ⽂文件和编程创建
initWithFrame:
• 当编程创建⼀一个 view 时,我们使⽤用 alloc 和⼀一个
 initializer message,就像我们创建任何其他对象
 ⼀一样
• UIView 的 designated initializer,同样也是
 HypnosisView的,是:initWithFrame:
• initWithFrame: 采⽤用 CGRect 结构作为参数,这个
 CGRect 就是 view 的边框
view 的 frame
• 每个视图实例都有⼀一个 frame 矩形
• 视图的 frame 指定了视图的⼤大⼩小和其相对于它的
 superview 的位置
• frame 由 CGRect 结构体表⽰示,并且包含成员 origin
 和 size。这些成员也是结构体
• orgin 是 CGPoint 类型,包含两个 float 成员:x 和 y
• size 是 CGSize 类型,包含两个 float 成员: width
 和 height
• 结构体 structure 并不是 objective-c 对象,因此不
 能给它们发送消息
CGRect
• view 总是⼀一个矩形
再画⼀一个 HypnosisView 作为 Window的 subview




 // 再创建⼀一个 HypnosisView
 CGRect anotherFrame = CGRectMake(20, 30, 50, 50);
 HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:anotherFrame];
 [anotherView setBackgroundColor:[UIColor blueColor]];

 [[self window] addSubview:anotherView];
两个 HypnosisViews 都作为 window
  的 subview 的 view hierarchy
将⼀一个 HypnosisView 作为另⼀一个 HypnosisView 的
    subview

      • ⼀一个 view 的 frame 是
        相对于它的 superview
        的,⽽而不是 window



[[self window] addSubview:anotherView];
[view addSubview:anotherView];
⼀一个 HypnosisView 作为另⼀一个
HypnosisView 的 subview 的 view hierarchy
drawRect: ⽅方法
• 截⾄至⺫⽬目前,我们创建了⼀一个 UIView 的⼦子类,创建
 了两个实例,把它们插⼊入到了 view hierarchy

• 我们给这两个实例不同的 backgroundColor 以便
 区分它们在屏幕上的位置和⼤大⼩小。组成 iOS 所有
 界⾯面的 view,要能够绘制更多,⽽而不仅仅是带颜
 ⾊色的矩形

• 使视图变得有趣的绘制都发⽣生在 UIView 的
 drawRect: ⽅方法中

• 默认情况下,drawRect: 什么都不做。UIView ⼦子类
 通过重写这个⽅方法来实现⾃自定义绘制。
Core Graphic framework
• 在我们重写 drawRect: 时,我们发出创建 UIView
 ⼦子类的实例图像的绘制指令,这些绘制指令都来
 ⾃自 Core Graphics framework。
• 这个框架在创建新项⺫⽬目时被⾃自动添加到
 application target
绘制上下⽂文(drawing context)
• 重写 drawRect: 的第⼀一步是获取 drawing context
 (绘制上下⽂文)的指针
• 绘制上下⽂文维护绘制的状态(例如当前绘制的颜
 ⾊色和画笔的厚度)和执⾏行绘制操作
• 绘制操作会使⽤用当前的绘制状态进⾏行绘制
• 在 drawRect: 结束时,上下⽂文⽣生成的图像就变成了
 view 的图像
CGContextRef
• CGContextRef 被定义为 CGContext * - 指向
 CGContext 的指针
• Ref 后缀使得很容易区分指向 C 结构体的指针和指
 向 Objective-C 对象的指针
• 这⾥里的 ctx 指向了当前的绘制上下⽂文
- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
}
bounds
• view 的图像和它出现在屏幕上是⼀一样⼤大⼩小,也就
 是说和 view 的 frame ⼤大⼩小⼀一致
• frame 描述了 view 相对于 view 的 superview 的⼤大
 ⼩小
• UIView 的名为 bounds 的 CGRect 属性给出了视图
 和它的 superview ⽆无关的⼤大⼩小
• 在 CGContextRef 上执⾏行的绘制操作必须落在
 bounds 矩形区域内,否则会被剪切到这个矩形区
 域
在 bounds 矩形中⼼心绘制⼀一个圆形
- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect bounds = [self bounds];

    // 计算出 bounds 矩形的中⼼心
    CGPoint center;
    center.x = bounds.origin.x + bounds.size.width / 2.0;
    center.y = bounds.origin.y + bounds.size.height / 2.0;

    // 圆的半径应该是和 view 的⼤大⼩小⼏几乎⼀一样的
    float maxRadius = hypot(bounds.size.width, bounds.size.height) / 4.0;

    // 线条宽度应该是 10pt 宽
    CGContextSetLineWidth(ctx, 10);

    // 线条的颜⾊色应该是灰⾊色(red/green/blue = 0.6, alpha = 1.0)
    CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);

    // 增加⼀一个矩形到上下⽂文 - 这不会真正绘制矩形
    CGContextAddArc(ctx, center.x, center.y, maxRadius, 0.0, M_PI * 2.0, YES);

    // 执⾏行绘制指令;使⽤用当前状态绘制当前形状
    CGContextStrokePath(ctx);
}
在 bounds 中⼼心绘制圆形
backgroundColor
• 在 HyposisterAppDelegate.m 中,移除设置 view
  的背景颜⾊色的代码
• 在 HypnosisView.m 中,重写 initWithFrame: 来设
  置每个 HyposisView 的背景颜⾊色为 clear
     HypnosisView *view = [[HypnosisView alloc]
 initWithFrame:viewFrame];
     [view setBackgroundColor:[UIColor redColor]];
     [[self window] addSubview:view];
     CGRect anotherFrame = CGRectMake(20, 30, 50, 50);
     HypnosisView *anotherView = [[HypnosisView alloc]
 initWithFrame:anotherFrame];
     [anotherView setBackgroundColor:[UIColor blueColor]];

 - (id)initWithFrame:(CGRect)frame
 {
     self = [super initWithFrame:frame];
     if (self) {
         [self setBackgroundColor:[UIColor clearColor]];
     }
     return self;
 }
默认填充颜⾊色 和 clearColor
Core Graphics
Core Graphics
• 以 CG 开头的函数和类型都来⾃自于 Core Graphics
 framework, ⼀一套⽤用于 2D 绘图的 C 语⾔言 API
• Core Graphics framework 的中⼼心是 CGContextRef: 所
 有其他的 Core Graphics 函数和类型都以某种⽅方式和
 绘制上下⽂文进⾏行交互,然后由上下⽂文来创建图像
• 前⾯面⽤用到的 Core Graphics 函数:
 • 使⽤用 CGContextSetLineWidth 设置绘制状态
 • 使⽤用 CGContextSetRGBStrokeColor 设置描边颜⾊色
 • 使⽤用 CGContextAddArc 增加⼀一个 path 到上下⽂文(Arc
  只是 path 的⼀一种)
绘制操作
• 路径被添加到上下⽂文之后,我们就可以执⾏行⼀一个
绘制操作了。三种绘制操作:
• CGContextStrokePath
 • 沿着路径绘制线条(描边)
• CGContextFillPaht
 • 填充由路径构成的形状
• CGContextClip
 • 限制对由路径定义的区域的进⼀一步绘制操作
绘制⼀一系列的同⼼心圆
       • ⼀一个绘制操作完成后,当前路径就被从上下⽂文中
          移除
       • 这样,要绘制多于⼀一个圆形的话,就需要为每个
          圆形添加⼀一个路径到上下⽂文
     // 圆的半径应该是和 view 的⼤大⼩小⼏几乎⼀一样的
//     float maxRadius = hypot(bounds.size.width, bounds.size.height) / 4.0;
     float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;

     // 线条宽度应该是 10pt 宽
     CGContextSetLineWidth(ctx, 10);

     // 线条的颜⾊色应该是灰⾊色(red/green/blue = 0.6, alpha = 1.0)
     CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);

     for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
         CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
         CGContextStrokePath(ctx);
     }
同⼼心圆绘制
• 再改造⼀一下,去掉后
来加的那个
HypnosisView

• 把剩下的
HypnosisView 的⼤大⼩小
改成跟屏幕⼀一般⼤大
修改 didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication
*)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen]
bounds]];

     // 创建⼀一个 HypnosisView,⼤大⼩小和屏幕⼤大⼩小⼀一样
⼤大
    HypnosisView *view = [[HypnosisView
alloc] initWithFrame:[[self window]
bounds]];
    [[self window] addSubview:view];

    // Override point for customization
after application launch.
    self.window.backgroundColor = [UIColor
whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
UIKit 绘制
• 有⼀一些 Foundation 或者 UIKit 的类可以和
 CGContextRef ⼀一起⼯工作
UIColor
     • 例如:UIColor,⼀一个 UIColor 的实例表⽰示⼀一种颜
       ⾊色,可以⽤用来设置上下⽂文当前⽤用来绘制的颜⾊色
     • 可以把 CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6,
       1.0) 替换为 [[UIColor colorWithRed: 0.6 green:0.6 blue:
       0.6 alpha:1] setStroke];
     • UIColor 也预先准备了很多常⽤用的颜⾊色供使⽤用,可
       以把上⾯面的代码改成:[[UIColor lightGrayColor]
       setStroke];

CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
[[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1] setStroke];
NSString
• NSString 能够绘制到 CGContextRef
• 发送 drawInRect:withFont: 消息给⼀一个 NSString 将
 会使⽤用给定的字体把这个字符串绘制到当前上下
 ⽂文给定的矩形内
• 在 HyposisView.m 中的 drawRect: 中代码的末尾增
 加⼀一段代码
 NSString *text = @"你快被催眠了.";
 UIFont *font = [UIFont boldSystemFontOfSize:28];

 CGRect textRect;
 textRect.size = [text sizeWithFont:font];

 textRect.origin.x = center.x - textRect.size.width / 2.0;
 textRect.origin.y = center.y - textRect.size.height / 2.0;

 [[UIColor blackColor] setFill];

 [text drawInRect:textRect withFont:font];
绘制⽂文本
使⽤用阴影

   // 设置当前上下⽂文填充⾊色为⿊黑⾊色
   [[UIColor blackColor] setFill];

   // 增加阴影。阴影将会向右移动4个points,向下移动3个points
   CGSize offset = CGSizeMake(4, 3);
   // 阴影颜⾊色使⽤用深灰⾊色
   CGColorRef color = [[UIColor darkGrayColor] CGColor];
   // 使⽤用这些参数设置上下⽂文的阴影,后续绘制都会使⽤用阴影 (blur,模
糊,2.0)
   CGContextSetShadowWithColor(ctx, offset, 2.0, color);

   // 绘制字符串
   [text drawInRect:textRect withFont:font];
带阴影的⽂文字
UIImage
• 有⼀一个类似的有⽤用的⽅方法 drawInRect: ⽤用于绘制⼀一
 个 image 对象到⼀一个上下⽂文
重绘视图
• 当⼀一个 UIView 实例收到 setNeedsDisplay 消息
 时,会重绘它的图像

• 当视图⼦子类可绘制内容变更时,会给⾃自⾝身发送
 setNeedsDisplay 消息
 • 例如⼀一个 UILabel 当它被发送 setText: 消息时会为重
  新显⽰示⽽而标记⾃自⾝身(如果显⽰示的⽂文本变更的话必须要
  重新绘制图像)

• 重绘操作不会⽴立即执⾏行,⽽而是在 run loop 中进⾏行
• 重绘和组合
使⽤用 Run Loop 重绘视图
增加 circleColor 属性
• HypnosisView.h 中声明 circleColor 属性
• HypnosisView.m 中 synthesize 这个属性
• 更新 initWithFrame: ⽅方法创建⼀一个默认的
 circleColor
• 在 drawRect: 中设置上下⽂文描边颜⾊色使⽤用
 circleColor ⽽而不是 light gray
增加 circleColor (代码)
@interface HypnosisView : UIView
@property (nonatomic, strong) UIColor *circleColor;
@end



@implementation HypnosisView
@synthesize circleColor;



- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setBackgroundColor:[UIColor clearColor]];
        [self setCircleColor:[UIColor lightGrayColor]];
    }
    return self;
}



   CGContextSetLineWidth(ctx, 10);
   [[self circleColor] setStroke];
   for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
       CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
       CGContextStrokePath(ctx);
   }
动作事件(Motion Events)
• UIView 的超类 UIResponder 的实例,可以在设备
 被晃动或者键盘上的按键被按下时成为 window
 的 first responder 并将接收事件

• 下⾯面我们把 HypnosisView 的实例作为 Hypnosister
 window 的 first responder ,晃动设备将发送消息
 给 HypnosisView,并且这个⽅方法将改变它的
 circleColor
becomeFirstResponder
• 在 HypnosisterAppDelegate.m 中告诉
 HypnosisView 实例成为 first responder
• becomeFirstResponder ⽅方法返回⼀一个布尔值,标明
 接收对象是否成功的变成了 window 的 first
 responder
  BOOL success = [view becomeFirstResponder];
  if (success) {
      NSLog(@"HypnosisView 成为 first responder");
  } else {
      NSLog(@"⽆无法成为 first responder");
  }
canBecomeFirstResponder
• ⼤大多数 UIResponder 对象收到
  becomeFirstResponder 时返回 NO
• 因为⼤大多数的视图,默认情况下只关⼼心和它们关联
  的事件,并且它们(⼏几乎)总有机会处理这些事件
• 举例来说,不管谁是 first responder ⼀一个被点击的
  UIButton 总会被发送消息
• 所以,⼀一个 responder 对象必须显式表明它希望变
  成 first responder
• 在 HypnosisView.m 中重写UIResponder 的
  canBecomeFirstResponder 来返回 YES
- (BOOL)canBecomeFirstResponder{   return YES;   }
动作事件⽅方法(motion event methods)

 • 接收事件的⽅方法也是在 UIResponder 中实现的,
    为了实例能够响应事件它们也必须在 UIResponder
    的⼦子类中重写
 • 为了处理 shakes,在 UIResponder ⼦子类中重写的
    ⽅方法被称为 motion event methods(动作事件⽅方
    法),其声明类似:

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
motionBegan:withEvent:
   • 为了让 HypnosisView 能够知道⽤用户开始摇动设备
     并且采取⾏行动,必须实现 motionBegan:withEvent:
     ⽅方法
   • 在 HypnosisView.m 中重写这个⽅方法以便开始摇动
     的时候改变 circleColor
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent
*)event
{
    NSLog(@"设备开始摇动");
    [self setCircleColor:[UIColor redColor]];
}
发送 setNeedsDisplay 消息
 • 当 HypnosisView 的 circleColor 改变时,实例变量
   circleColor 被设置指向⼀一个新的 UIColor 实例
 • 但是当这些发⽣生时,我们并没有告诉
   HypnosisView 它需要重新绘制它的图像;我们必
   须在 HypnosisView 改变了它的 circleColor 后给它
   发送 setNeedsDisplay 消息
 • 在 HypnosisView.m 中,实现 setCircleColor: 以在变
   更 circleColor 实例变量后发送这个消息
 • 此时再运⾏行应⽤用,摇动设备,圆形就变成了红⾊色
- (void)setCircleColor:(UIColor *)clr
{
    circleColor = clr;
    [self setNeedsDisplay];
}
UIEventSubtype
  • UIEventSubtype 持有触发这个⽅方法的动作事件的
     类型。根据类型定义,处理 shake 事件外还可能
     会有其他事件也触发这个⽅方法
  • 在 HypnosisView.m 中 增加⼀一⾏行⽤用于判断的代码:
     if (motion == UIEventSubtypeMotionShake)
typedef NS_ENUM(NSInteger, UIEventSubtype) {
    UIEventSubtypeNone                                =   0,
    UIEventSubtypeMotionShake                         =   1,
    UIEventSubtypeRemoteControlPlay                   =   100,
    UIEventSubtypeRemoteControlPause                  =   101,
    UIEventSubtypeRemoteControlStop                   =   102,
    UIEventSubtypeRemoteControlTogglePlayPause        =   103,
    UIEventSubtypeRemoteControlNextTrack              =   104,
    UIEventSubtypeRemoteControlPreviousTrack          =   105,
    UIEventSubtypeRemoteControlBeginSeekingBackward   =   106,
    UIEventSubtypeRemoteControlEndSeekingBackward     =   107,
    UIEventSubtypeRemoteControlBeginSeekingForward    =   108,
    UIEventSubtypeRemoteControlEndSeekingForward      =   109,
};
使⽤用 UIScrollView
• 当我们希望能够让⽤用户滚动显⽰示我们视图的时
候,我们⼀一般会使我们的视图成为 UIScrollView 的
subview
UIScrollView 对象⽰示意图
contentSize
• 滚动视图⼀一般⽤用于⽐比屏幕⼤大的视图,滚动视图绘
 制它的 subview 的⼀一个矩形部分,在滚动视图上
 移动你的⼿手指或者平移时,改变的是 subview 的
 矩形的位置
• 可以把滚动视图看作是⼀一个观察⼝口(view
 port),滚动视图的⼤大⼩小就是这个观察⼝口的⼤大⼩小
• 能够查看的区域⼤大⼩小是 UIScrollView 的 contentSize,
 ⼀一般就是UIScrollView 的 subview 的⼤大⼩小
UIScrollView 及其内容区域
超⼤大号的 HypnosisView
• 在 HypnosisterAppDelegate.m 中创建⼀一个超⼤大号
 的 HypnosisView
• 把⼤大号的 HypnosisView 放到⼀一个 scroll view 中
• 把 scroll view 添加到 window
超⼤大号的 HypnosisView(代码)

    // 创建⼀一个和 window ⼀一样⼤大的 UIScrollView
    CGRect screenRect = [[self window] bounds];
    UIScrollView *scrollView = [[UIScrollView alloc]
initWithFrame:screenRect];

    [[self window] addSubview:scrollView];

    // 创建⼀一个是屏幕两倍⼤大的 HypnosisView,并添加到scroll view
    CGRect bigRect = screenRect;
    bigRect.size.width *= 2.0;
    bigRect.size.height *= 2.0;
    HypnosisView *view = [[HypnosisView alloc]
initWithFrame:bigRect];
    // 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window
    [scrollView addSubview:view];

    // 告诉 scrollview 它的区域有多⼤大
    [scrollView setContentSize:bigRect.size];
超⼤大号的 HypnosisView
平移和分⻚页(panning and paging)
• 前⾯面我们⽤用 scroll view 来移动特别⼤大的 view
• scroll view 也可以⽤用来在⼀一些不同的 view 实例之
 间进⾏行平移
• ⽐比如我们有两个屏幕⼤大⼩小的视图,⽤用户可以在它
 们之间平移
• 我们把前⾯面例⼦子中的 HypnosisView 再缩回跟原来
 屏幕⼀一样⼤大⼩小,并且增加另外⼀一个同样⼤大⼩小的
 HypnosisView 作为 UIScrollView 的 subview
• 同样,把 contentSize 设成屏幕宽度的两倍,⾼高度
 ⼀一样
平移(代码)

     // 创建⼀一个和 window ⼀一样⼤大的 UIScrollView
     CGRect screenRect = [[self window] bounds];
     UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];

     [[self window] addSubview:scrollView];

     CGRect bigRect = screenRect;
     bigRect.size.width *= 2.0;
     // 把⾼高度改成和屏幕⼀一样
//     bigRect.size.height *= 2.0;
//     HypnosisView *view = [[HypnosisView alloc] initWithFrame:bigRect];
     HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect];
     // 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window
     [scrollView addSubview:view];

     // 把另⼀一个 HypnosisView 的矩形移动右边屏幕外
     screenRect.origin.x = screenRect.size.width;
     // 创建另外⼀一个 HypnosisView
     HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:screenRect];
     [scrollView addSubview:anotherView];

     // 同样告诉 scrollview 它的区域有多⼤大
     [scrollView setContentSize:bigRect.size];
在两个 HypnosisView 间平移
setPagingEnable:
 • 前⾯面移动视图的时候可以停在两个 HypnosisView 之间
 • 要强制滚动视图的观察⼝口捕捉这些视图之⼀一,在
   HypnosisterAppDelegate.m 中为 scroll view 打开
   paging(分⻚页)
 • 这样再平移到两个视图中间时,就会⾃自动滚动到其中
   ⼀一个视图
 • paging 通过滚动视图的 contentSize / bounds 来分成
   同样⼤大⼩小的 section 进⾏行显⽰示
CGRect screenRect = [[self window] bounds];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
// 打开 paging
[scrollView setPagingEnabled:YES];
[[self window] addSubview:scrollView];
缩放(Zooming)
• UIScrollView 还以可以放⼤大或者缩⼩小其内容
• 要进⾏行缩放,scroll view 需要知道最⼩小和最⼤大缩放
 级别,⽽而且需要知道要进⾏行缩放的视图
• 把 HypnosisterAppDelegate.m 中的另⼀一个
 HypnosisView 去掉,同时把 UIScrollView 的
 contentSize 改回屏幕⼤大⼩小
• 然后禁⽤用 paging,设置 zoom 属性,设置
 UIScrollView 的 delegate
Zooming(代码)


CGRect screenRect = [[self window] bounds];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
// 设置缩放属性
[scrollView setMinimumZoomScale:1.0];
[scrollView setMaximumZoomScale:5.0];
// 设置 scrollView 的 delegate
[scrollView setDelegate:self];

[[self window] addSubview:scrollView];

CGRect bigRect = screenRect;
HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect];
// 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window
[scrollView addSubview:view];

// 同样告诉 scrollview 它的区域有多⼤大(现在 scroll view 是屏幕⼤大⼩小)
[scrollView setContentSize:bigRect.size];
viewForZoomingInScrollView:
• 构建并运⾏行,我们可以看到⼀一个 HypnosisView,
 但是此时我们既不能平移也不能缩放
• 要实现缩放,必须在 UIScrollView 的 delegate 中
 实现 viewForZoomingInScrollView: ⽅方法
• 这个⽅方法返回在其上进⾏行缩放的 view 的实例,这
 ⾥里的这个 view 应该是 HypnosisView 的实例,
 UIScrollView 的 delegate 将是
 HypnosisterAppDelegate
实现 viewForZoomingInScrollView
• 在 HypnosisterAppDelegate.h 中,声明
 HypnosisterAppDelegate 遵守
 UIScrollViewDelegate
• 在 HypnosisterAppDelegate 中把指向
 HypnosisView 的本地变量改成实例变量,以便在
 viewForZoomingInScrollView: 中可以访问
• 实现 viewForZoomingInScroolView 来返回这个
 view
实现 viewForZoomingInScrollView(代码)
#import <UIKit/UIKit.h>
// 导⼊入 HypnosisView 头⽂文件
#import "HypnosisView.h"

@interface HypnosisterAppDelegate : UIResponder <UIApplicationDelegate,
UIScrollViewDelegate>
{
    HypnosisView *view;
}
@property (strong, nonatomic) UIWindow *window;
@end


//     HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect];
     view = [[HypnosisView alloc] initWithFrame:screenRect];

     [scrollView addSubview:view];


- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return view;
}
Zooming HypnosisView
• 在模拟器上可以通过
 按住 Option 键来模拟
 两个⼿手指,然后点击
 并移动⿏鼠标
隐藏状态栏
 • 在窗⼝口可⻅见前隐藏状态栏
 • 在 HypnosisterAppDelegate.m 中的
  application:didFinishLaunchingWithOptions: 接近
  顶部的地⽅方添加⼀一⾏行代码
 • 再构建并运⾏行时,会发现状态栏在应⽤用程序启动
  以后会逐渐消失(fading out)
    // 隐藏状态栏 status bar
    [[UIApplication sharedApplication]
setStatusBarHidden:YES
withAnimation:UIStatusBarAnimationFade];
设置属性在显⽰示前隐藏状态栏
• 也可以通过往应⽤用程序的信息属性列表(info
 property list)中添加⼀一个新的键值对在应⽤用程序
 出现在屏幕前隐藏状态栏
• 选择 Hypnosister target,然后选择 Info ⾯面板,在
 最后⼀一⾏行点 “+” 图标,会显⽰示⼀一个新⾏行
• 在新⾏行的 Key 列中,选择 “Status bar is initially
 hidden”, 在 Value 列中把值从NO改成 YES
隐藏状态栏的 Info property list
隐藏了状态栏的窗⼝口

Mais conteúdo relacionado

Semelhante a 06 Subclassing UIView and UIScrollView

07 View Controllers
07 View Controllers07 View Controllers
07 View ControllersTom Fan
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableViewTom Fan
 
使用Qt+和open gl®+创建跨平台可视化ui
使用Qt+和open gl®+创建跨平台可视化ui使用Qt+和open gl®+创建跨平台可视化ui
使用Qt+和open gl®+创建跨平台可视化uilsdsjy
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCellTom Fan
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001rainx1982
 
09 UITableView and UITableViewController
09 UITableView and UITableViewController09 UITableView and UITableViewController
09 UITableView and UITableViewControllerTom Fan
 
12 Camera
12 Camera12 Camera
12 CameraTom Fan
 
ASP.NET Core 2.1設計新思維與新發展
ASP.NET  Core 2.1設計新思維與新發展ASP.NET  Core 2.1設計新思維與新發展
ASP.NET Core 2.1設計新思維與新發展江華 奚
 
怎樣在 Flutter app 中使用 Google Maps
怎樣在 Flutter app 中使用 Google Maps怎樣在 Flutter app 中使用 Google Maps
怎樣在 Flutter app 中使用 Google MapsWeizhong Yang
 
Html5移动网站开发实践
Html5移动网站开发实践Html5移动网站开发实践
Html5移动网站开发实践Web Zhao
 
twMVC#01 | ASP.NET MVC 的第一次親密接觸
twMVC#01 | ASP.NET MVC 的第一次親密接觸twMVC#01 | ASP.NET MVC 的第一次親密接觸
twMVC#01 | ASP.NET MVC 的第一次親密接觸twMVC
 
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例Will Huang
 
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPiOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPMing-Sian Lin
 
Backbone js and requirejs
Backbone js and requirejsBackbone js and requirejs
Backbone js and requirejsChi-wen Sun
 

Semelhante a 06 Subclassing UIView and UIScrollView (20)

07 View Controllers
07 View Controllers07 View Controllers
07 View Controllers
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableView
 
I os 14
I os 14I os 14
I os 14
 
View Animation
View AnimationView Animation
View Animation
 
使用Qt+和open gl®+创建跨平台可视化ui
使用Qt+和open gl®+创建跨平台可视化ui使用Qt+和open gl®+创建跨平台可视化ui
使用Qt+和open gl®+创建跨平台可视化ui
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCell
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001
 
005
005005
005
 
Retina mac
Retina macRetina mac
Retina mac
 
09 UITableView and UITableViewController
09 UITableView and UITableViewController09 UITableView and UITableViewController
09 UITableView and UITableViewController
 
12 Camera
12 Camera12 Camera
12 Camera
 
ASP.NET Core 2.1設計新思維與新發展
ASP.NET  Core 2.1設計新思維與新發展ASP.NET  Core 2.1設計新思維與新發展
ASP.NET Core 2.1設計新思維與新發展
 
I os 16
I os 16I os 16
I os 16
 
怎樣在 Flutter app 中使用 Google Maps
怎樣在 Flutter app 中使用 Google Maps怎樣在 Flutter app 中使用 Google Maps
怎樣在 Flutter app 中使用 Google Maps
 
Html5移动网站开发实践
Html5移动网站开发实践Html5移动网站开发实践
Html5移动网站开发实践
 
I os 02
I os 02I os 02
I os 02
 
twMVC#01 | ASP.NET MVC 的第一次親密接觸
twMVC#01 | ASP.NET MVC 的第一次親密接觸twMVC#01 | ASP.NET MVC 的第一次親密接觸
twMVC#01 | ASP.NET MVC 的第一次親密接觸
 
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
 
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPiOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
 
Backbone js and requirejs
Backbone js and requirejsBackbone js and requirejs
Backbone js and requirejs
 

Mais de Tom Fan

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统Tom Fan
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workersTom Fan
 
Web sockets
Web socketsWeb sockets
Web socketsTom Fan
 
Semantics
SemanticsSemantics
SemanticsTom Fan
 
Multimedia
MultimediaMultimedia
MultimediaTom Fan
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5Tom Fan
 
Html5 history
Html5 historyHtml5 history
Html5 historyTom Fan
 
Geolocation
GeolocationGeolocation
GeolocationTom Fan
 
File api
File apiFile api
File apiTom Fan
 
Deviceaccess
DeviceaccessDeviceaccess
DeviceaccessTom Fan
 
Webstorage
WebstorageWebstorage
WebstorageTom Fan
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分Tom Fan
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状Tom Fan
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发Tom Fan
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Tom Fan
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型Tom Fan
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaultsTom Fan
 
17 Localization
17 Localization17 Localization
17 LocalizationTom Fan
 

Mais de Tom Fan (20)

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
 
Web sockets
Web socketsWeb sockets
Web sockets
 
Storage
StorageStorage
Storage
 
Semantics
SemanticsSemantics
Semantics
 
Multimedia
MultimediaMultimedia
Multimedia
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
 
Html5 history
Html5 historyHtml5 history
Html5 history
 
Geolocation
GeolocationGeolocation
Geolocation
 
File api
File apiFile api
File api
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
 
Css3
Css3Css3
Css3
 
Webstorage
WebstorageWebstorage
Webstorage
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaults
 
17 Localization
17 Localization17 Localization
17 Localization
 

06 Subclassing UIView and UIScrollView

  • 1. ⼦子类化 UIView 和 UIScrollView 范圣刚,princetoad@gmail.com,www.tfan.org
  • 2. 什么是 view? • 我们在前⾯面创建过 UIButton,UILabel等,但是究 竟什么是 view? • ⼀一个 view 是 UIView 或者它的某⼀一⼦子类的实例 • view 知道如何把它⾃自⼰己绘制到应⽤用程序窗⼝口上(⼀一个 UIWindow 的实例) • view 存在于 view 的层次结构中 view hierarchy。view hierarchy 的根是应⽤用程序窗⼝口 • view 要处理事件,⽐比如触控事件
  • 3. ⾃自定义 UIView ⼦子类 • 使⽤用同⼼心圆填满屏幕 • 绘制⽂文本 • 启⽤用滚动和放⼤大缩⼩小
  • 4. Hypnosister(催眠应⽤用) • 创建⼀一个新的 iOS 项⺫⽬目,空项⺫⽬目 • 产品名:Hypnosister • 类前缀:Hypnosister • 勾选 “Use Automatic Reference Counting”
  • 6. • 视图构成了应⽤用的⽤用户界⾯面 • 每个视图维护⼀一个⽤用于表⽰示它的图像,例如 UIButton 的图像就是⼀一个在中间带有标题的圆⾓角 矩形;⼀一个 UILabel 的图像就是简单的⽂文本 • 当关于 view 的⼀一些东⻄西变化时,例如 UILabel 的 text 属性或 UIButton 的 title,view 的图像被重绘 以便这些变更能够在屏幕上变得可⻅见
  • 7. UIWindow • UIWindow 是 UIView 的⼦子类 • 每个应⽤用程序恰好有⼀一个 UIWindow 的实例作为 在应⽤用中所有视图的容器,当应⽤用启动的时候窗 ⼝口被创建 • HypnosisterAppDelegate.m 中的 application:didFinishLaunchingWithOptions: ⽅方 法中创建 UIWindow 对象并发送消息 makeKeyAndVisible - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 8. subview • window 被放到屏幕上以后,就可以在它上⾯面增加 其他 view,这样的 view 就叫做 window 的 subview • 作为 window 的 subview 的 view 也可以具有 subviews,结果就形成了⼀一个视图对象的层次结 构 • 当且仅当⼀一个视图被添加到这个层次结构以后才 会在屏幕上显⽰示,⽆无论它是作为 window 的⼀一个 subview,还是作为另外⼀一个已经添加到 window 的 view 的 subview • 因此,window 是 view hierarchy 的 root
  • 9. redrawn(重绘) • 当屏幕被重绘的时候,⾸首先是 window 的图像被 绘制到屏幕上 • 然后,window 的所有 subview 把它们⾃自⼰己的图像 绘制到屏幕上 • 接着,subviews 的 subviews 再绘制它们的图像, 依此类推
  • 11. ⽤用户界⾯面⽣生成 • 创建每⼀一个 view 的图像并且把每⼀一个 view 加到 view hierarchy • 类似 UIButton,MKMapView 以及 UITextField 这些 (Apple本⾝身提供的)已经知道它们的图像看起来 是什么样⼦子 • 另外⼀一种情况是我们需要创建⼀一个⾃自定义的视图 对象并且编写代码来创建它的图像
  • 13. • ⼦子类化 UIView,并且⾃自定义⼦子类的图像 • 创建⼀一个类 HypnosisView, 从 UIView 继承 • 在 HypnosisterAppDelegate.m 中引⼊入 HypnosisView.h 头⽂文件 • 在 application:didFinishLaunchingWithOptions: 中创建 HypnosisView 的实例并把它作为 window 的 subview 添加到 view hierarchy 中
  • 14. 创建 HypnosisView 的实例并添 加到UIWindow - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; CGRect viewFrame = CGRectMake(160, 240, 100, 150); HypnosisView *view = [[HypnosisView alloc] initWithFrame:viewFrame]; [view setBackgroundColor:[UIColor redColor]]; [[self window] addSubview:view]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 15. subview 和 superview • 红⾊色的 HypnosisView 实例 在⽩白⾊色的 UIWindow 之上绘 制;HypnosisView 实例是 UIWindow 的 subview • 把⼀一个 view 作为另外⼀一个 view 的 subview 添加时,反 向关系同时也⾃自动建⽴立了, HypnosisView 的 superview 是 UIWindow • XIB ⽂文件和编程创建
  • 16. initWithFrame: • 当编程创建⼀一个 view 时,我们使⽤用 alloc 和⼀一个 initializer message,就像我们创建任何其他对象 ⼀一样 • UIView 的 designated initializer,同样也是 HypnosisView的,是:initWithFrame: • initWithFrame: 采⽤用 CGRect 结构作为参数,这个 CGRect 就是 view 的边框
  • 17. view 的 frame • 每个视图实例都有⼀一个 frame 矩形 • 视图的 frame 指定了视图的⼤大⼩小和其相对于它的 superview 的位置 • frame 由 CGRect 结构体表⽰示,并且包含成员 origin 和 size。这些成员也是结构体 • orgin 是 CGPoint 类型,包含两个 float 成员:x 和 y • size 是 CGSize 类型,包含两个 float 成员: width 和 height • 结构体 structure 并不是 objective-c 对象,因此不 能给它们发送消息
  • 19. 再画⼀一个 HypnosisView 作为 Window的 subview // 再创建⼀一个 HypnosisView CGRect anotherFrame = CGRectMake(20, 30, 50, 50); HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:anotherFrame]; [anotherView setBackgroundColor:[UIColor blueColor]]; [[self window] addSubview:anotherView];
  • 20. 两个 HypnosisViews 都作为 window 的 subview 的 view hierarchy
  • 21. 将⼀一个 HypnosisView 作为另⼀一个 HypnosisView 的 subview • ⼀一个 view 的 frame 是 相对于它的 superview 的,⽽而不是 window [[self window] addSubview:anotherView]; [view addSubview:anotherView];
  • 24. • 截⾄至⺫⽬目前,我们创建了⼀一个 UIView 的⼦子类,创建 了两个实例,把它们插⼊入到了 view hierarchy • 我们给这两个实例不同的 backgroundColor 以便 区分它们在屏幕上的位置和⼤大⼩小。组成 iOS 所有 界⾯面的 view,要能够绘制更多,⽽而不仅仅是带颜 ⾊色的矩形 • 使视图变得有趣的绘制都发⽣生在 UIView 的 drawRect: ⽅方法中 • 默认情况下,drawRect: 什么都不做。UIView ⼦子类 通过重写这个⽅方法来实现⾃自定义绘制。
  • 25. Core Graphic framework • 在我们重写 drawRect: 时,我们发出创建 UIView ⼦子类的实例图像的绘制指令,这些绘制指令都来 ⾃自 Core Graphics framework。 • 这个框架在创建新项⺫⽬目时被⾃自动添加到 application target
  • 26. 绘制上下⽂文(drawing context) • 重写 drawRect: 的第⼀一步是获取 drawing context (绘制上下⽂文)的指针 • 绘制上下⽂文维护绘制的状态(例如当前绘制的颜 ⾊色和画笔的厚度)和执⾏行绘制操作 • 绘制操作会使⽤用当前的绘制状态进⾏行绘制 • 在 drawRect: 结束时,上下⽂文⽣生成的图像就变成了 view 的图像
  • 27. CGContextRef • CGContextRef 被定义为 CGContext * - 指向 CGContext 的指针 • Ref 后缀使得很容易区分指向 C 结构体的指针和指 向 Objective-C 对象的指针 • 这⾥里的 ctx 指向了当前的绘制上下⽂文 - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); }
  • 28. bounds • view 的图像和它出现在屏幕上是⼀一样⼤大⼩小,也就 是说和 view 的 frame ⼤大⼩小⼀一致 • frame 描述了 view 相对于 view 的 superview 的⼤大 ⼩小 • UIView 的名为 bounds 的 CGRect 属性给出了视图 和它的 superview ⽆无关的⼤大⼩小 • 在 CGContextRef 上执⾏行的绘制操作必须落在 bounds 矩形区域内,否则会被剪切到这个矩形区 域
  • 29. 在 bounds 矩形中⼼心绘制⼀一个圆形 - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGRect bounds = [self bounds]; // 计算出 bounds 矩形的中⼼心 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; // 圆的半径应该是和 view 的⼤大⼩小⼏几乎⼀一样的 float maxRadius = hypot(bounds.size.width, bounds.size.height) / 4.0; // 线条宽度应该是 10pt 宽 CGContextSetLineWidth(ctx, 10); // 线条的颜⾊色应该是灰⾊色(red/green/blue = 0.6, alpha = 1.0) CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0); // 增加⼀一个矩形到上下⽂文 - 这不会真正绘制矩形 CGContextAddArc(ctx, center.x, center.y, maxRadius, 0.0, M_PI * 2.0, YES); // 执⾏行绘制指令;使⽤用当前状态绘制当前形状 CGContextStrokePath(ctx); }
  • 31. backgroundColor • 在 HyposisterAppDelegate.m 中,移除设置 view 的背景颜⾊色的代码 • 在 HypnosisView.m 中,重写 initWithFrame: 来设 置每个 HyposisView 的背景颜⾊色为 clear HypnosisView *view = [[HypnosisView alloc] initWithFrame:viewFrame]; [view setBackgroundColor:[UIColor redColor]]; [[self window] addSubview:view]; CGRect anotherFrame = CGRectMake(20, 30, 50, 50); HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:anotherFrame]; [anotherView setBackgroundColor:[UIColor blueColor]]; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setBackgroundColor:[UIColor clearColor]]; } return self; }
  • 34. Core Graphics • 以 CG 开头的函数和类型都来⾃自于 Core Graphics framework, ⼀一套⽤用于 2D 绘图的 C 语⾔言 API • Core Graphics framework 的中⼼心是 CGContextRef: 所 有其他的 Core Graphics 函数和类型都以某种⽅方式和 绘制上下⽂文进⾏行交互,然后由上下⽂文来创建图像 • 前⾯面⽤用到的 Core Graphics 函数: • 使⽤用 CGContextSetLineWidth 设置绘制状态 • 使⽤用 CGContextSetRGBStrokeColor 设置描边颜⾊色 • 使⽤用 CGContextAddArc 增加⼀一个 path 到上下⽂文(Arc 只是 path 的⼀一种)
  • 35. 绘制操作 • 路径被添加到上下⽂文之后,我们就可以执⾏行⼀一个 绘制操作了。三种绘制操作: • CGContextStrokePath • 沿着路径绘制线条(描边) • CGContextFillPaht • 填充由路径构成的形状 • CGContextClip • 限制对由路径定义的区域的进⼀一步绘制操作
  • 36. 绘制⼀一系列的同⼼心圆 • ⼀一个绘制操作完成后,当前路径就被从上下⽂文中 移除 • 这样,要绘制多于⼀一个圆形的话,就需要为每个 圆形添加⼀一个路径到上下⽂文 // 圆的半径应该是和 view 的⼤大⼩小⼏几乎⼀一样的 // float maxRadius = hypot(bounds.size.width, bounds.size.height) / 4.0; float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0; // 线条宽度应该是 10pt 宽 CGContextSetLineWidth(ctx, 10); // 线条的颜⾊色应该是灰⾊色(red/green/blue = 0.6, alpha = 1.0) CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0); for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES); CGContextStrokePath(ctx); }
  • 38. 修改 didFinishLaunchingWithOptions: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // 创建⼀一个 HypnosisView,⼤大⼩小和屏幕⼤大⼩小⼀一样 ⼤大 HypnosisView *view = [[HypnosisView alloc] initWithFrame:[[self window] bounds]]; [[self window] addSubview:view]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 39. UIKit 绘制 • 有⼀一些 Foundation 或者 UIKit 的类可以和 CGContextRef ⼀一起⼯工作
  • 40. UIColor • 例如:UIColor,⼀一个 UIColor 的实例表⽰示⼀一种颜 ⾊色,可以⽤用来设置上下⽂文当前⽤用来绘制的颜⾊色 • 可以把 CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0) 替换为 [[UIColor colorWithRed: 0.6 green:0.6 blue: 0.6 alpha:1] setStroke]; • UIColor 也预先准备了很多常⽤用的颜⾊色供使⽤用,可 以把上⾯面的代码改成:[[UIColor lightGrayColor] setStroke]; CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0); [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1] setStroke];
  • 41. NSString • NSString 能够绘制到 CGContextRef • 发送 drawInRect:withFont: 消息给⼀一个 NSString 将 会使⽤用给定的字体把这个字符串绘制到当前上下 ⽂文给定的矩形内 • 在 HyposisView.m 中的 drawRect: 中代码的末尾增 加⼀一段代码 NSString *text = @"你快被催眠了."; UIFont *font = [UIFont boldSystemFontOfSize:28]; CGRect textRect; textRect.size = [text sizeWithFont:font]; textRect.origin.x = center.x - textRect.size.width / 2.0; textRect.origin.y = center.y - textRect.size.height / 2.0; [[UIColor blackColor] setFill]; [text drawInRect:textRect withFont:font];
  • 43. 使⽤用阴影 // 设置当前上下⽂文填充⾊色为⿊黑⾊色 [[UIColor blackColor] setFill]; // 增加阴影。阴影将会向右移动4个points,向下移动3个points CGSize offset = CGSizeMake(4, 3); // 阴影颜⾊色使⽤用深灰⾊色 CGColorRef color = [[UIColor darkGrayColor] CGColor]; // 使⽤用这些参数设置上下⽂文的阴影,后续绘制都会使⽤用阴影 (blur,模 糊,2.0) CGContextSetShadowWithColor(ctx, offset, 2.0, color); // 绘制字符串 [text drawInRect:textRect withFont:font];
  • 45. UIImage • 有⼀一个类似的有⽤用的⽅方法 drawInRect: ⽤用于绘制⼀一 个 image 对象到⼀一个上下⽂文
  • 47. • 当⼀一个 UIView 实例收到 setNeedsDisplay 消息 时,会重绘它的图像 • 当视图⼦子类可绘制内容变更时,会给⾃自⾝身发送 setNeedsDisplay 消息 • 例如⼀一个 UILabel 当它被发送 setText: 消息时会为重 新显⽰示⽽而标记⾃自⾝身(如果显⽰示的⽂文本变更的话必须要 重新绘制图像) • 重绘操作不会⽴立即执⾏行,⽽而是在 run loop 中进⾏行 • 重绘和组合
  • 48. 使⽤用 Run Loop 重绘视图
  • 49. 增加 circleColor 属性 • HypnosisView.h 中声明 circleColor 属性 • HypnosisView.m 中 synthesize 这个属性 • 更新 initWithFrame: ⽅方法创建⼀一个默认的 circleColor • 在 drawRect: 中设置上下⽂文描边颜⾊色使⽤用 circleColor ⽽而不是 light gray
  • 50. 增加 circleColor (代码) @interface HypnosisView : UIView @property (nonatomic, strong) UIColor *circleColor; @end @implementation HypnosisView @synthesize circleColor; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setBackgroundColor:[UIColor clearColor]]; [self setCircleColor:[UIColor lightGrayColor]]; } return self; } CGContextSetLineWidth(ctx, 10); [[self circleColor] setStroke]; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES); CGContextStrokePath(ctx); }
  • 52. • UIView 的超类 UIResponder 的实例,可以在设备 被晃动或者键盘上的按键被按下时成为 window 的 first responder 并将接收事件 • 下⾯面我们把 HypnosisView 的实例作为 Hypnosister window 的 first responder ,晃动设备将发送消息 给 HypnosisView,并且这个⽅方法将改变它的 circleColor
  • 53. becomeFirstResponder • 在 HypnosisterAppDelegate.m 中告诉 HypnosisView 实例成为 first responder • becomeFirstResponder ⽅方法返回⼀一个布尔值,标明 接收对象是否成功的变成了 window 的 first responder BOOL success = [view becomeFirstResponder]; if (success) { NSLog(@"HypnosisView 成为 first responder"); } else { NSLog(@"⽆无法成为 first responder"); }
  • 54. canBecomeFirstResponder • ⼤大多数 UIResponder 对象收到 becomeFirstResponder 时返回 NO • 因为⼤大多数的视图,默认情况下只关⼼心和它们关联 的事件,并且它们(⼏几乎)总有机会处理这些事件 • 举例来说,不管谁是 first responder ⼀一个被点击的 UIButton 总会被发送消息 • 所以,⼀一个 responder 对象必须显式表明它希望变 成 first responder • 在 HypnosisView.m 中重写UIResponder 的 canBecomeFirstResponder 来返回 YES - (BOOL)canBecomeFirstResponder{ return YES; }
  • 55. 动作事件⽅方法(motion event methods) • 接收事件的⽅方法也是在 UIResponder 中实现的, 为了实例能够响应事件它们也必须在 UIResponder 的⼦子类中重写 • 为了处理 shakes,在 UIResponder ⼦子类中重写的 ⽅方法被称为 motion event methods(动作事件⽅方 法),其声明类似: - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
  • 56. motionBegan:withEvent: • 为了让 HypnosisView 能够知道⽤用户开始摇动设备 并且采取⾏行动,必须实现 motionBegan:withEvent: ⽅方法 • 在 HypnosisView.m 中重写这个⽅方法以便开始摇动 的时候改变 circleColor - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { NSLog(@"设备开始摇动"); [self setCircleColor:[UIColor redColor]]; }
  • 57. 发送 setNeedsDisplay 消息 • 当 HypnosisView 的 circleColor 改变时,实例变量 circleColor 被设置指向⼀一个新的 UIColor 实例 • 但是当这些发⽣生时,我们并没有告诉 HypnosisView 它需要重新绘制它的图像;我们必 须在 HypnosisView 改变了它的 circleColor 后给它 发送 setNeedsDisplay 消息 • 在 HypnosisView.m 中,实现 setCircleColor: 以在变 更 circleColor 实例变量后发送这个消息 • 此时再运⾏行应⽤用,摇动设备,圆形就变成了红⾊色 - (void)setCircleColor:(UIColor *)clr { circleColor = clr; [self setNeedsDisplay]; }
  • 58. UIEventSubtype • UIEventSubtype 持有触发这个⽅方法的动作事件的 类型。根据类型定义,处理 shake 事件外还可能 会有其他事件也触发这个⽅方法 • 在 HypnosisView.m 中 增加⼀一⾏行⽤用于判断的代码: if (motion == UIEventSubtypeMotionShake) typedef NS_ENUM(NSInteger, UIEventSubtype) { UIEventSubtypeNone = 0, UIEventSubtypeMotionShake = 1, UIEventSubtypeRemoteControlPlay = 100, UIEventSubtypeRemoteControlPause = 101, UIEventSubtypeRemoteControlStop = 102, UIEventSubtypeRemoteControlTogglePlayPause = 103, UIEventSubtypeRemoteControlNextTrack = 104, UIEventSubtypeRemoteControlPreviousTrack = 105, UIEventSubtypeRemoteControlBeginSeekingBackward = 106, UIEventSubtypeRemoteControlEndSeekingBackward = 107, UIEventSubtypeRemoteControlBeginSeekingForward = 108, UIEventSubtypeRemoteControlEndSeekingForward = 109, };
  • 62. contentSize • 滚动视图⼀一般⽤用于⽐比屏幕⼤大的视图,滚动视图绘 制它的 subview 的⼀一个矩形部分,在滚动视图上 移动你的⼿手指或者平移时,改变的是 subview 的 矩形的位置 • 可以把滚动视图看作是⼀一个观察⼝口(view port),滚动视图的⼤大⼩小就是这个观察⼝口的⼤大⼩小 • 能够查看的区域⼤大⼩小是 UIScrollView 的 contentSize, ⼀一般就是UIScrollView 的 subview 的⼤大⼩小
  • 64. 超⼤大号的 HypnosisView • 在 HypnosisterAppDelegate.m 中创建⼀一个超⼤大号 的 HypnosisView • 把⼤大号的 HypnosisView 放到⼀一个 scroll view 中 • 把 scroll view 添加到 window
  • 65. 超⼤大号的 HypnosisView(代码) // 创建⼀一个和 window ⼀一样⼤大的 UIScrollView CGRect screenRect = [[self window] bounds]; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect]; [[self window] addSubview:scrollView]; // 创建⼀一个是屏幕两倍⼤大的 HypnosisView,并添加到scroll view CGRect bigRect = screenRect; bigRect.size.width *= 2.0; bigRect.size.height *= 2.0; HypnosisView *view = [[HypnosisView alloc] initWithFrame:bigRect]; // 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window [scrollView addSubview:view]; // 告诉 scrollview 它的区域有多⼤大 [scrollView setContentSize:bigRect.size];
  • 67. 平移和分⻚页(panning and paging) • 前⾯面我们⽤用 scroll view 来移动特别⼤大的 view • scroll view 也可以⽤用来在⼀一些不同的 view 实例之 间进⾏行平移 • ⽐比如我们有两个屏幕⼤大⼩小的视图,⽤用户可以在它 们之间平移 • 我们把前⾯面例⼦子中的 HypnosisView 再缩回跟原来 屏幕⼀一样⼤大⼩小,并且增加另外⼀一个同样⼤大⼩小的 HypnosisView 作为 UIScrollView 的 subview • 同样,把 contentSize 设成屏幕宽度的两倍,⾼高度 ⼀一样
  • 68. 平移(代码) // 创建⼀一个和 window ⼀一样⼤大的 UIScrollView CGRect screenRect = [[self window] bounds]; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect]; [[self window] addSubview:scrollView]; CGRect bigRect = screenRect; bigRect.size.width *= 2.0; // 把⾼高度改成和屏幕⼀一样 // bigRect.size.height *= 2.0; // HypnosisView *view = [[HypnosisView alloc] initWithFrame:bigRect]; HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect]; // 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window [scrollView addSubview:view]; // 把另⼀一个 HypnosisView 的矩形移动右边屏幕外 screenRect.origin.x = screenRect.size.width; // 创建另外⼀一个 HypnosisView HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:screenRect]; [scrollView addSubview:anotherView]; // 同样告诉 scrollview 它的区域有多⼤大 [scrollView setContentSize:bigRect.size];
  • 70. setPagingEnable: • 前⾯面移动视图的时候可以停在两个 HypnosisView 之间 • 要强制滚动视图的观察⼝口捕捉这些视图之⼀一,在 HypnosisterAppDelegate.m 中为 scroll view 打开 paging(分⻚页) • 这样再平移到两个视图中间时,就会⾃自动滚动到其中 ⼀一个视图 • paging 通过滚动视图的 contentSize / bounds 来分成 同样⼤大⼩小的 section 进⾏行显⽰示 CGRect screenRect = [[self window] bounds]; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect]; // 打开 paging [scrollView setPagingEnabled:YES]; [[self window] addSubview:scrollView];
  • 71. 缩放(Zooming) • UIScrollView 还以可以放⼤大或者缩⼩小其内容 • 要进⾏行缩放,scroll view 需要知道最⼩小和最⼤大缩放 级别,⽽而且需要知道要进⾏行缩放的视图 • 把 HypnosisterAppDelegate.m 中的另⼀一个 HypnosisView 去掉,同时把 UIScrollView 的 contentSize 改回屏幕⼤大⼩小 • 然后禁⽤用 paging,设置 zoom 属性,设置 UIScrollView 的 delegate
  • 72. Zooming(代码) CGRect screenRect = [[self window] bounds]; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect]; // 设置缩放属性 [scrollView setMinimumZoomScale:1.0]; [scrollView setMaximumZoomScale:5.0]; // 设置 scrollView 的 delegate [scrollView setDelegate:self]; [[self window] addSubview:scrollView]; CGRect bigRect = screenRect; HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect]; // 把 HypnosisView 作为 subview 添加到 scrollview,⽽而不是 window [scrollView addSubview:view]; // 同样告诉 scrollview 它的区域有多⼤大(现在 scroll view 是屏幕⼤大⼩小) [scrollView setContentSize:bigRect.size];
  • 73. viewForZoomingInScrollView: • 构建并运⾏行,我们可以看到⼀一个 HypnosisView, 但是此时我们既不能平移也不能缩放 • 要实现缩放,必须在 UIScrollView 的 delegate 中 实现 viewForZoomingInScrollView: ⽅方法 • 这个⽅方法返回在其上进⾏行缩放的 view 的实例,这 ⾥里的这个 view 应该是 HypnosisView 的实例, UIScrollView 的 delegate 将是 HypnosisterAppDelegate
  • 74. 实现 viewForZoomingInScrollView • 在 HypnosisterAppDelegate.h 中,声明 HypnosisterAppDelegate 遵守 UIScrollViewDelegate • 在 HypnosisterAppDelegate 中把指向 HypnosisView 的本地变量改成实例变量,以便在 viewForZoomingInScrollView: 中可以访问 • 实现 viewForZoomingInScroolView 来返回这个 view
  • 75. 实现 viewForZoomingInScrollView(代码) #import <UIKit/UIKit.h> // 导⼊入 HypnosisView 头⽂文件 #import "HypnosisView.h" @interface HypnosisterAppDelegate : UIResponder <UIApplicationDelegate, UIScrollViewDelegate> { HypnosisView *view; } @property (strong, nonatomic) UIWindow *window; @end // HypnosisView *view = [[HypnosisView alloc] initWithFrame:screenRect]; view = [[HypnosisView alloc] initWithFrame:screenRect]; [scrollView addSubview:view]; - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return view; }
  • 76. Zooming HypnosisView • 在模拟器上可以通过 按住 Option 键来模拟 两个⼿手指,然后点击 并移动⿏鼠标
  • 77. 隐藏状态栏 • 在窗⼝口可⻅见前隐藏状态栏 • 在 HypnosisterAppDelegate.m 中的 application:didFinishLaunchingWithOptions: 接近 顶部的地⽅方添加⼀一⾏行代码 • 再构建并运⾏行时,会发现状态栏在应⽤用程序启动 以后会逐渐消失(fading out) // 隐藏状态栏 status bar [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
  • 78. 设置属性在显⽰示前隐藏状态栏 • 也可以通过往应⽤用程序的信息属性列表(info property list)中添加⼀一个新的键值对在应⽤用程序 出现在屏幕前隐藏状态栏 • 选择 Hypnosister target,然后选择 Info ⾯面板,在 最后⼀一⾏行点 “+” 图标,会显⽰示⼀一个新⾏行 • 在新⾏行的 Key 列中,选择 “Status bar is initially hidden”, 在 Value 列中把值从NO改成 YES