• Cocoa Touch(三):图形界面UIKit、Core Animation、Core Graphics


    UIKit 视图树模型

    1、视图树模型

    计算机图形实际上是一个视图树模型,每个视图都有一个本地坐标系。
    每个本地坐标系的组成部分是:原点在父坐标系中的位置,每个基在父坐标系中的位置,由此就可以根据向量的本地位置求出相对于父坐标系的位置,最终求出向量全局位置。
    我们要分清全局坐标系,父坐标系,本地坐标系三种概念,分清基、向量的坐标、向量的位置三种概念。

    2、控件监听事件

    观察者模式在这里得到充分体现。

    UIView和UIControll都可以监听用户事件,但是UIView实现监听事件要通过在子类中覆写- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event等方法,或者借助于UIGestureRecognizer。而UIControll类则为线程提供了一些方便的接口,比如addTarget方法。

    监听普通事件:

    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];

     监听手势事件:

    UITapGestureRecognizer响应顺序是怎么样的
    一个scrollview上有几个按钮
    在scrollview上add 了一个单击事件

    singletap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [singletap setNumberOfTapsRequired:1];
    [scrollview addGestureRecognizer:singletap];
    这样点击按钮,不会响应按钮的事件,而会直接跳到handleSingleTap去了,原因在于单击事件应该先被直接单击的superview处理,如果没有处理才传递到subview去处理。
    怎么才能让按钮响应单击事件?

    使用UIGestureRecognizerDelegate的一个方法判断点击的是哪个view,确定是否响应事件。

    singletap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [singletap setNumberOfTapsRequired:1];
    singletap.delegate = self;
    [scrollview addGestureRecognizer:singletap];
    
    //让scrollview而不是上面的button来相应触摸事件
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if(touch.view != scrollview){
            return NO;
        }else
            return YES;
    }

     再一次举例

    UITapGestureRecognizer *singleFingerTap = 
      [[UITapGestureRecognizer alloc] initWithTarget:self 
                                              action:@selector(handleSingleTap:)];
    [self.view addGestureRecognizer:singleFingerTap];
    [singleFingerTap release];
    
    - (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
      CGPoint location = [recognizer locationInView:[recognizer.view superview]];
    
      //Do stuff here...
    }

    3、添加视图

    [[[UIApplication sharedApplication] keyWindow] addSubview:myNewView];

    怎样让视图显示在最外层?这样就把视图显示在最外层,一个类似action sheet或者alert view的效果就是在window上添加一层灰色透明视图作为根节点,再添加想要的视图。

     尝尝使用这个方法覆盖一个HUD风格的透明图形

    becomeKeyWindow 和 – resignKeyWindow ,线程调用这两个方法,控制一个window实例是否成为用于转发用户消息的那个窗口

    Core Animation 动画

    1、实现动画

    对于不需要和用户交互的动画,要实现动画只需要调用UIView的一个类方法即可,不必涉及CALayer和用户触摸事件

    UIViewAnimationOptions options = UIViewAnimationCurveLinear | UIViewAnimationOptionAllowUserInteraction;  
    [UIView animateWithDuration:0.2 delay:0.0 options:options animations:^  
     {  
         highlightView.alpha = 1.0;    
     } completion: ^(BOOL finished){ 
        // 动画结束时的处理
     }];  

    但是对于交互类型的动画,则需要手势识别器,实现视图跟随触摸点移动,或者覆写需要覆写touchesBegan&touchesMoved:配合Core Graphics中的各种函数实现自定义绘图。 

    2、CALayer树

    线程会利用CALayer类(或者其子类)封装的代码来进行界面绘图。UIView继承自UIResponder,主要特点是可以告诉线程如何响应触摸事件,而CALayer是告诉线程如何进行实际的绘图。

    UIView的实例中维护着一个CALayer实例的树模型,相当于CALayer树的管理器,访问UIView的跟绘图和跟坐标有关的属性,例如frame,bounds等等,实际上内部都是在访问它所包含的CALayer的相关属性。

    UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示

    - (class) layerClass {
    
      return ([CAEAGLLayer class]);
    
    }

    UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表示。例如下面的代码会在目标View上敷上一层黑色的透明薄膜。

    grayCover = [[CALayer alloc] init];
    grayCover.backgroundColor = [[[UIColor blackColor]
        colorWithAlphaComponent:0.2] CGColor];
    [self.layer addSubLayer: grayCover];

    UIView的layer树形在系统内部,被系统维护着三份copy
    第一份,逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
    第二份,动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
    第三份,显示树,这棵树的内容是当前正被显示在屏幕上的内容。
    这三棵树的逻辑结构都是一样的,区别只有各自的属性。

    3、CATrainsition

    一个CATrainsition类的实例可以引用一个动画过程。可以通过设置CATrainsition实例的属性,定义将要如何进行动画。

    CATransition是CAAnimation的子类,通过静态方法animation获取可以引用动画过程的实例。

    自定义在window上添加模态视图的全翻页效果:

    CATransition *animation = [CATransition animation]; animation.duration = 1.0; 
    animation.timingFunction = UIViewAnimationCurveEaseInOut; 
    animation.type = @"pageCurl"; 
    //animation.type = kCATransitionPush; 
    animation.subtype = kCATransitionFromLeft; 
    [self.view.window.layer addAnimation:animation forKey:nil]; 
    [self presentViewController:myNextViewController animated:NO completion:nil];

     修改导航控制器推入动画

    CATransition *transition = [CATransition animation];
    transition.duration = 1;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromTop;
    transition.delegate = self;
    [self.navigationController.view.layer addAnimation:transition forKey:nil];
    self.navigationController.navigationBarHidden = NO;
    [self.navigationController pushViewController:viewController animated:NO];

    修改导航控制器弹出的动画

    CATransition *transition = [CATransition animation];
    transition.duration =0.4;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionReveal;
    //transition.subtype = kCATransitionFromBottom;
    transition.delegate = self;
    [self.navigationController.view.layer addAnimation:transition forKey:nil];
    self.navigationController.navigationBarHidden = NO;
    [self.navigationController popViewControllerAnimated:NO];

    Core Graphics 绘图

    iPhone的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩展UIView 的类,并重写drawRect方法,在这里进行绘图操作,线程会自动调用此方法进行绘图。

    比如,你想绘制一个方块,你需要写一个类来扩展UIView并在drawRect方法中填入如下代码:

    - (void)drawRect:(CGRect)rect { 
    // Drawing code. 
    //获得处理的上下文 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    //设置线条样式 
    CGContextSetLineCap(context, kCGLineCapSquare); 
    //设置线条粗细宽度 
    CGContextSetLineWidth(context, 1.0); 
    
    //设置颜色 
    CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); 
    //开始一个起始路径 
    CGContextBeginPath(context); 
    //起始点设置为(0,0):注意这是上下文对应区域中的相对坐标, 
    CGContextMoveToPoint(context, 0, 0); 
    //设置下一个坐标点 
    CGContextAddLineToPoint(context, 100, 100); 
    //设置下一个坐标点 
    CGContextAddLineToPoint(context, 0, 150); 
    //设置下一个坐标点 
    CGContextAddLineToPoint(context, 50, 180); 
    //连接上面定义的坐标点 
    CGContextStrokePath(context); 
    
    }

    不过有时不会直接调用在context上添加线段的addLine方法,逐点填写绝对坐标,而是先获得上下文context,在定义一个路径path,调用addLine方法定义好一个path的全部位置,然后把路径添加到上下文里

    class MyView: UIView {
        var _path = CGPathCreateMutable()
    
        override func drawRect(rect: CGRect) {
            UIColor.redColor().set()
            var context = UIGraphicsGetCurrentContext()
            CGContextSetLineWidth(context, 5)
            CGContextAddPath(context, _path)
            CGContextStrokePath(context)
        }
    
        override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
            let p = (touches as NSSet).anyObject()?.locationInView(self)
            CGPathMoveToPoint(path, nil, p!.x, p!.y)
        }
    
        override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
            let p = (touches as NSSet).anyObject()?.locationInView(self)
            CGPathAddLineToPoint(_path, nil, p!.x, p!.y)
            setNeedsDisplay()
        }
    
        func drawTwoLines(point:CGPoint, lineJoin:CGLineJoin) {
            let context = UIGraphicsGetCurrentContext()
            CGContextSetLineJoin(context, lineJoin)
            CGContextSetLineWidth(context, 5)
            CGContextMoveToPoint(context, point.x-100, point.y+50)
            CGContextAddLineToPoint(context, point.x, point.y)
            CGContextAddLineToPoint(context, point.x+100, point.y+50)
            CGContextStrokePath(context)
        }
    }

    再说明一下重绘,重绘操作仍然在drawRect方法中完成,但是苹果不建议直接调用drawRect方法,如果你强直直接调用此方法,当然是没有效果的。在UIView中,重写drawRect: (CGRect) aRect方法可以自己定义想要画的图案,但是此方法一般情况下只会画一次,也就是说这个drawRect方法一般情况下只会被调用一次。drawRect是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的.

    苹果要求我们调用UIView类中的setNeedsDisplay方法,则程序会自动调用drawRect方法进行重绘,即[self setNeedsDisplay]方法即可。

    注意
    1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。
    2、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
    3、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0.
    4、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect ,让系统自动调该方法。

    5、若使用calayer绘图,只能在drawInContext: 中绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法。
    6、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕

    Interface Builder

    笔者倾向于使用纯代码方式来实现界面布局,这样更容易实现自定义UI控件和更容易维护修改。不过对于简单的界面,也可以使用苹果为我们提供的图形化的方式来构建。

    1、如果通过加载nib文件的方式加载controller的视图,当线程调用 initWithNibName: 来初始化controller.view时,如果nibName可以找到,线程会根据nib文件初始化控制器,需要注意的是无论通过什么方式(除了story board)初始化控制器,最终都会调用UIViewController或者其子类中的initWithNibName方法,所以在这里初始化就够了。

    2、如果通过story board生成控制器,那么会调用instantiateViewControllerWithIdentifier创建对象,然后用initWithCdoer初始化控制器。

    - (id)initWithCoder:(NSCoder*)coder{
        if (self =[super initWithcoder:coder]) {
            //初始化控制器的变量
        }
        return self;
    }

     3、使用interface builder实现控件监听事件,需要配合代码中的IBOutlet和IBAction关键字,并建立Interface Builder对象和代码的关联。

    素材资源

    要建立一个像那么回事儿的界面,不能不关注图片资源,虽然这看起来像是美工的活。

    1、分辨率

    iPhone4/4s:主屏幕像素:960x640,开发中屏幕尺寸为 480x320

    iPhone5/5s:主屏幕像素:1136x640,开发中屏幕尺寸为 568x320

    iPhone6/6s:主屏幕像素:1334x750,开发中屏幕尺寸为 667x375

    iPhone6p:主屏幕像素:1920*1080,开发中屏幕尺寸为 736x414

    一定要注意开发是的最小刻度值和并不等于实际像素的长度。

    素材准备:

    ● Icon.png – 像素57×57 iPhone iOS 6应用图标
    ● Icon-120.png – 像素120 x 120 iPhone4显示屏 iOS 7应用图标
    ● Icon-Small.png – 像素29×29 iPhone 系统设置和搜索结果图标
    ● Icon-Small@2x.png – 像素58×58 iPhone Retina显示屏 系统设置和搜索结果图标

    ● Default.png 480x320 iPhone启动画面图片

    ● Default@2x.png 960x640 iPhone4、4s的启动画面图片

    ● Default-568h@2x.png 1136x640 iPhone5启动画面图片

    详见 

    https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html

    程序中需要注意bounds和nativeBounds的区别。iphone5的bounds.size.height是568.

    实用界面设置

    UIApplication(或者其子类)的对象主要告诉线程如何等待和处理事件。

    这个类中定义了很多有用的代码,可供线程执行。

    1.设置icon上的数字图标
    [UIApplication sharedApplication].applicationIconBadgeNumber = 4;
    2.设置摇动手势的时候,是否支持redo,undo操作
    [UIApplication sharedApplication].applicationSupportsShakeToEdit =YES;
    3.判断程序运行状态
    if([UIApplication sharedApplication].applicationState ==UIApplicationStateInactive){
    NSLog(@"程序在运行状态");
    }
    4.阻止屏幕变暗进入休眠状态
    [UIApplication sharedApplication].idleTimerDisabled =YES;
    慎重使用本功能,因为非常耗电。
    5.显示联网状态
    [UIApplication sharedApplication].networkActivityIndicatorVisible =YES;
    6.在map上显示一个地址
    NSString* addressText =@"1 Infinite Loop, Cupertino, CA 95014";
    addressText = [addressText stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
    NSString* urlText = [NSString stringWithFormat:@"http://maps.google.com/maps?q=%@", addressText];
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:urlText]];
    7.发送电子邮件
    NSString *recipients =@"mailto:first@example.com?cc=second@example.com,third@example.com&subject=Hello from California!";
    NSString *body =@"&body=It is raining in sunny California!";
    NSString *email = [NSString stringWithFormat:@"%@%@", recipients, body];
    email = [email stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:email]];
    8.打电话到一个号码
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"tel://8004664411"]];
    9.发送短信 
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"sms://466453"]];
    10.打开一个网址
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"http://itunesconnect.apple.com"]];
    
    
    AppDelegate类
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    当由于其它方法打开应用程序(如URL指定或者连接),通知委托启动完毕 
    - (void)applicationWillTerminate:(UIApplication *)application 
    通知委托,应用程序将在关闭 退出,请做一些清理工作。 
    - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application 
    通知委托,应用程序收到了为来自系统的内存不足警告。-(void)applicationSignificantTimeChange:(UIApplication *)application 
    通知委托系统时间发生改变(主要是指时间属性,而不是具体的时间值) 
    打开URL 
    - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url 
    打开指定的URL 
    控制状态栏方位变化 
    – application:willChangeStatusBarOrientation:duration: 
    设备方向将要发生改变 
    – application:didChangeStatusBarOrientation: 
    活动状态改变 
    - (void)applicationWillResignActive:(UIApplication *)application 
    通知委托应用程序将进入非活动状态,在此期间,应用程序不接收消息或事件。-(void)applicationDidBecomeActive:(UIApplication *)application 
    通知委托应用程序进入活动状态,请恢复数据
    
    如果我们希望获得appdelegate实例,不必通过import类的方式,只需要:
    UIApplicationDelegate* delegate = [[UIApplication sharedApplication] delegate];

    实用控件和方法

    很多控件和方法都是直接在keywindow上添加视图实现,它们往往是以模态的方式呈现,适用于注册、登录等等场景。

    1、UIViewController:

    每个控制器实例都有下面两个方法,用于在keywindow上添加或删除一个模态视图,常用于登录界面、发微博界面等等。

    – presentViewController:animated:completion: 弹出,出现一个新视图,可以带动画效果,完成后可以做相应的执行函数经常为nil
    – dismissViewControllerAnimated:completion:退出一个新视图,可以带动画效果,完成后可以做相应的执行函数经常为nil

    2、action sheet

    actionSheet = [[UIActionSheet alloc] initWithTitle:title delegate:nil cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"相册", @"拍照",nil];
        
        picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 50, 320, 220)];
        picker.delegate = self;
        picker.dataSource = self;
        picker.showsSelectionIndicator = YES;
        
        [actionSheet addSubview:picker];
        [actionSheet showInView:self.view];

    参数说明:

    title:视图标题

    delegate:设置代理

    cancelButtonTitle:取消按钮的标题

    destructiveButtonTitle:特殊标记的按钮的标题

    otherButtonTitles:其他按钮的标题

    动作回调方法为:

    - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
        UIAlertView *alertView;
        switch (buttonIndex) {
            case 0:
                alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"点击相册事件" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
                [alertView show];
                break;
                 
            case 1:
                alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"以点击拍照事件" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
                [alertView show];
                break;
                 
            default:
                break;
        }
    }

    3、alert view

    在action sheet中,动作回调方法中就创建了alert view实例。

  • 相关阅读:
    图形与文本
    Cookie处理函数练习
    jspSmartUpload上传下载全攻略
    SmartUpload 上传图片
    无下拉菜单
    servlet中使用SmartUpload组件实现上传
    乱码
    DIV+CSS 要兼容 IE8.0 应注意些什么?
    虚拟目录中的web.config不被上级目录的web.config影响的处理
    ASP.NET抓取页面源代码
  • 原文地址:https://www.cnblogs.com/xinchrome/p/4898409.html
Copyright © 2020-2023  润新知