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

UIKit 視圖樹模型html

一、視圖樹模型ios

計算機圖形其實是一個視圖樹模型,每一個視圖都有一個本地座標系。
每一個本地座標系的組成部分是:原點在父座標系中的位置,每一個基在父座標系中的位置,由此就能夠根據向量的本地位置求出相對於父座標系的位置,最終求出向量全局位置。
咱們要分清全局座標系,父座標系,本地座標系三種概念,分清基、向量的座標、向量的位置三種概念。app

 

二、控件監聽事件iphone

觀察者模式在這裏獲得充分體現。ide

UIView和UIControll均可以監聽用戶事件,可是UIView實現監聽事件要經過在子類中覆寫- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event等方法,或者藉助於UIGestureRecognizer。而UIControll類則爲線程提供了一些方便的接口,好比addTarget方法。函數

監聽普通事件:oop

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

 監聽手勢事件:動畫

UITapGestureRecognizer響應順序是怎麼樣的
一個scrollview上有幾個按鈕
在scrollview上add 了一個單擊事件ui

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...
}

 

三、添加視圖

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

怎樣讓視圖顯示在最外層?這樣就把視圖顯示在最外層,一個相似action sheet或者alert view的效果就是在window上添加一層灰色透明視圖做爲根節點,再添加想要的視圖。

 嚐嚐使用這個方法覆蓋一個HUD風格的透明圖形

becomeKeyWindow 和 – resignKeyWindow ,線程調用這兩個方法,控制一個window實例是否成爲用於轉發用戶消息的那個窗口

 

Core Animation 動畫

一、實現動畫

對於不須要和用戶交互的動畫,要實現動畫只須要調用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中的各類函數實現自定義繪圖。 

 

二、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的屬性等等就在這一份。
第二份,動畫樹,這是一箇中間層,系統正在這一層上更改屬性,進行各類渲染操做。
第三份,顯示樹,這棵樹的內容是當前正被顯示在屏幕上的內容。
這三棵樹的邏輯結構都是同樣的,區別只有各自的屬性。

三、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]方法便可。

注意
一、若是在UIView初始化時沒有設置rect大小,將直接致使drawRect不被自動調用。
二、經過設置contentMode屬性值爲UIViewContentModeRedraw。那麼將在每次設置或更改frame的時候自動調用drawRect:。
三、直接調用setNeedsDisplay,或者setNeedsDisplayInRect:觸發drawRect:,可是有個前提條件是rect不能爲0.
四、若使用UIView繪圖,只能在drawRect:方法中獲取相應的contextRef並繪圖。若是在其餘方法中獲取將獲取到一個invalidate的ref而且不能用於畫圖。drawRect:方法不能手動顯示調用,必須經過調用setNeedsDisplay 或者 setNeedsDisplayInRect ,讓系統自動調該方法。

五、若使用calayer繪圖,只能在drawInContext: 中繪製,或者在delegate中的相應方法繪製。一樣也是調用setNeedDisplay等間接調用以上方法。
六、若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕

 

Interface Builder

筆者傾向於使用純代碼方式來實現界面佈局,這樣更容易實現自定義UI控件和更容易維護修改。不過對於簡單的界面,也可使用蘋果爲咱們提供的圖形化的方式來構建。

一、若是經過加載nib文件的方式加載controller的視圖,當線程調用 initWithNibName: 來初始化controller.view時,若是nibName能夠找到,線程會根據nib文件初始化控制器,須要注意的是不管經過什麼方式(除了story board)初始化控制器,最終都會調用UIViewController或者其子類中的initWithNibName方法,因此在這裏初始化就夠了。

二、若是經過story board生成控制器,那麼會調用instantiateViewControllerWithIdentifier建立對象,而後用initWithCdoer初始化控制器。

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

 三、使用interface builder實現控件監聽事件,須要配合代碼中的IBOutlet和IBAction關鍵字,並創建Interface Builder對象和代碼的關聯。

 

素材資源

要創建一個像那麼回事兒的界面,不能不關注圖片資源,雖然這看起來像是美工的活。

一、分辨率

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 iPhone四、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上添加視圖實現,它們每每是以模態的方式呈現,適用於註冊、登陸等等場景。

一、UIViewController:

每一個控制器實例都有下面兩個方法,用於在keywindow上添加或刪除一個模態視圖,經常使用於登陸界面、發微博界面等等。

– presentViewController:animated:completion: 彈出,出現一個新視圖,能夠帶動畫效果,完成後能夠作相應的執行函數常常爲nil
– dismissViewControllerAnimated:completion:退出一個新視圖,能夠帶動畫效果,完成後能夠作相應的執行函數常常爲nil

 

二、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;
    }
}

 

三、alert view

在action sheet中,動做回調方法中就建立了alert view實例。

相關文章
相關標籤/搜索