9.5.1 不使用界面布局檔案開發UI界面
如果打算使用純代碼來開發UI界面則不需要設計任何界面布局檔案沒有界面布局檔案也就不再需要使用自定義的視圖控制器。這樣程式可以直接在應用程式委托對象的application: didFinishLaunchingWithOptions:方法中建立UIWindow和應用程式界面——所有這些對象的建立都使用objective-C代碼來完成。
執行個體無界面布局檔案開發iOS應用
首先建立一個iOS的Empty Application應用。在建立iOS應用時選擇“Empty Application”項即可如圖9.35所示。
圖9.35 建立iOS的EmptyApplication應用
對于“Empty Application”類型的iOS應用Xcode隻生成應用程式委托類不會生成任何界面設計檔案也不會生成任何控制器類。
對于打算使用純代碼開發UI界面的開發方式來說我們的應用并不需要任何界面設計檔案也不需要任何控制器。程式隻要修改應用程式委托的application:didFinishLaunchingWithOptions:方法并在該方法中建立UI控件然後利用這些UI控件搭建應用程式界面即可。下面是修改過的application:didFinishLaunchingWithOptions:方法代碼。
程式清單codes/09/9.5/CodeUI/ CodeUI/FKAppDelegate.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<code>// 應用程式加載完成後将會自動回調該方法</code>
<code>- (</code><code>BOOL</code><code>)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions</code>
<code>{</code>
<code> </code><code>// 建立UIWindow對象并将該UIWindow初始化為與螢幕相同大小</code>
<code> </code><code>self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];</code>
<code> </code><code>// 設定UIWindow的背景色</code>
<code> </code><code>self.window.backgroundColor = [UIColor whiteColor];</code>
<code> </code><code>// 建立一個UIViewController對象</code>
<code> </code><code>UIViewController* controller = [[UIViewController alloc] init];</code>
<code> </code><code>// 讓該程式的視窗加載并顯示viewController視圖控制器關聯的使用者界面</code>
<code> </code><code>self.window.rootViewController = controller;</code>
<code> </code><code>// 建立一個UIView對象</code>
<code> </code><code>UIView* rootView = [[UIView alloc] initWithFrame</code>
<code> </code><code>:[[UIScreen mainScreen] bounds]];</code>
<code> </code><code>// 設定controller顯示rootView控件</code>
<code> </code><code>controller.view = rootView;</code>
<code> </code><code>// 建立一個圓角按鈕</code>
<code> </code><code>UIButton* button = [UIButton buttonWithType: UIButtonTypeRoundedRect];</code>
<code> </code><code>// 設定按鈕的大小</code>
<code> </code><code>button.frame = CGRectMake(120, 100, 80, 40);</code>
<code> </code><code>// 為按鈕設定文本</code>
<code> </code><code>[button setTitle:@</code><code>"确定"</code> <code>forState:UIControlStateNormal];</code>
<code> </code><code>// 将按鈕添加到rootView控件中</code>
<code> </code><code>[rootView addSubview:button];</code>
<code> </code><code>// 建立一個UILabel對象</code>
<code> </code><code>self.show = [[UILabel alloc] initWithFrame</code>
<code> </code><code>:CGRectMake(60 , 40 , 180 , 30)];</code>
<code> </code><code>// 将UILabel添加到rootView控件中</code>
<code> </code><code>[rootView addSubview:self.show];</code>
<code> </code><code>// 設定UILabel預設顯示的文本</code>
<code> </code><code>self.show.text = @</code><code>"初始文本"</code><code>;</code>
<code> </code><code>self.show.backgroundColor = [UIColor grayColor];</code>
<code> </code><code>// 為圓角按鈕的觸碰事件綁定事件處理方法</code>
<code> </code><code>[button addTarget:self action:@selector(clickHandler:)</code>
<code> </code><code>forControlEvents:UIControlEventTouchUpInside];</code>
<code> </code><code>// 将該UIWindow對象設為主視窗并顯示出來</code>
<code> </code><code>[self.window makeKeyAndVisible];</code>
<code> </code><code>return</code> <code>YES;</code>
<code>}</code>
上面的代碼中首先建立了一個UIWindow作為應用程式的視窗接下來建立一個UIView作為UIWindow顯示的根視圖需要借助一個UIViewController對象。
一旦程式中有了UIView作為容器接下來代碼歸納起來相當于隻有三行此處的三行是一種歸納說法并非實際隻有三行。
建立UI控件比如建立UILabel丶建立UIButton等。
調用addSubView:方法将UI控件添加到其他容器中。
多次調用UI控件的setter方法來設定UI控件的外觀丶行為。
上面代碼中為按鈕的觸碰事件綁定了clickHandler:事件處理方法是以程式還需要在應用程式委托類中定義該方法。方法代碼如下
<code>- (</code><code>void</code><code>) clickHandler:(id)sender</code>
<code> </code><code>self.show.text = @</code><code>"開始學習iOS吧"</code><code>;</code>
運作該程式單擊程式中的按鈕即可看到如圖9.36所示的效果。
通過上面的開發過程可以發現不管是通過純代碼來建立UI控件再将這些UI控件搭建成程式界面還是使用界面設計檔案來搭建程式界面其本質是相同的。它們的本質都是把UI控件當成小的積木塊然後将這些“積木塊”按自己的意願組合在一起就可以做成iOS應用的程式界面了。
需要指出的是使用純代碼方式來開發iOS應用并不是最好的開發方式這種方式不僅開發步驟異常煩瑣而且所有建立程式界面的代碼都由應用程式委托對象的方法負責完成這并不符合MVC設計原則是以不利于程式元件的解耦。通過學習這種開發方式我們可以更好地了解iOS應用中應用程式委托的作用同時也能更好地了解iOS程式界面的底層實作原理。
下面介紹一種更實用的代碼方式開發UI界面。
9.5.2 使用代碼建立UI界面
更實際的情況是在程式運作開始時程式已經具有一個初始的程式界面初始界面可能隻包含一個UIView在程式運作過程中程式需要根據使用者互動來動态添加丶删除UI控件。
在這種需求下我們可以通過Interface Builder來設計程式的初始界面接下來在程式運作過程中可以通過代碼建立UI控件再将UI控件添加到相應的父控件中即可。
執行個體動态添加丶删除标簽
首先建立一個iOS的Single View Application應用建立完成後該應用将自帶一個Main.storyboard界面設計檔案但我們并不打算修改該界面設計檔案而是直接在程式代碼中建立整個UI界面程式隻使用該界面檔案中的UIView作為容器即可。
接下來修改控制器類在控制器類的實作部分建立整個程式界面綁定事件處理方法。下面是控制類的實作部分代碼。
程式清單codes/09/9.5/DynaLabel/DynaLabel/FKViewController.m
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<code>#import "FKViewController.h"</code>
<code>// 定義FKViewController的擴充</code>
<code>@interface FKViewController ()</code>
<code>// 定義一個屬性來記錄所有動态添加的UILabel控件</code>
<code>@property (nonatomic, strong) NSMutableArray* labels;</code>
<code>@end</code>
<code>@implementation FKViewController</code>
<code>// 定義一個變量來記錄下一個将要添加的UILabel的位置</code>
<code>int</code> <code>nextY = 80;</code>
<code>- (</code><code>void</code><code>)viewDidLoad</code>
<code> </code><code>[super viewDidLoad];</code>
<code> </code><code>// 設定該view的背景色</code>
<code> </code><code>self.view.backgroundColor = [UIColor grayColor];</code>
<code> </code><code>// 初始化labels數組</code>
<code> </code><code>self.labels = [NSMutableArray array];</code>
<code> </code><code>// 建立UIButtonTypeRoundedRect類型的UIButton對象</code>
<code> </code><code>UIButton* addBn = [UIButton buttonWithType:UIButtonTypeRoundedRect];</code>
<code> </code><code>// 設定addBn的大小和位置</code>
<code> </code><code>addBn.frame = CGRectMake(30, 30, 60, 40);</code>
<code> </code><code>// 為UIButton設定按鈕文本</code>
<code> </code><code>[addBn setTitle:@</code><code>"添加"</code>
<code> </code><code>forState:UIControlStateNormal];</code>
<code> </code><code>// 為addBn的Touch Up Inside事件綁定事件處理方法</code>
<code> </code><code>[addBn addTarget:self action:@selector(add:)</code>
<code> </code><code>UIButton* removeBn = [UIButton buttonWithType:UIButtonTypeRoundedRect];</code>
<code> </code><code>// 設定removeBn的大小和位置</code>
<code> </code><code>removeBn.frame = CGRectMake(230, 30, 60, 40);</code>
<code> </code><code>[removeBn setTitle:@</code><code>"删除"</code>
<code> </code><code>// 為removeBn的Touch Up Inside事件綁定事件處理方法</code>
<code> </code><code>[removeBn addTarget:self action:@selector(</code><code>remove</code><code>:)</code>
<code> </code><code>[self.view addSubview:addBn];</code>
<code> </code><code>[self.view addSubview:removeBn];</code>
<code>- (</code><code>void</code><code>)add:(id)sender {</code>
<code> </code><code>// 建立一個UILabel控件</code>
<code> </code><code>UILabel* label = [[UILabel alloc] initWithFrame:</code>
<code> </code><code>CGRectMake(80, nextY, 160, 30)];</code>
<code> </code><code>label.text = @</code><code>"瘋狂iOS講義"</code><code>; </code><code>// 設定該UILabel顯示的文本</code>
<code> </code><code>[self.labels addObject: label]; </code><code>// 将該UILabel添加到labels數組中</code>
<code> </code><code>[self.view addSubview:label]; </code><code>// 将UILabel控件添加到view父控件内</code>
<code> </code><code>nextY += 50; </code><code>// 控制nextY的值加50</code>
<code>- (</code><code>void</code><code>)</code><code>remove</code><code>:(id)sender {</code>
<code> </code><code>// 如果labels數組中元素個數大于0表明有UILabel可删除</code>
<code> </code><code>if</code><code>([self.labels count] > 0)</code>
<code> </code><code>{</code>
<code> </code><code>// 将最後一個UILabel從界面上删除</code>
<code> </code><code>[[self.labels lastObject] removeFromSuperview];</code>
<code> </code><code>[self.labels removeLastObject]; </code><code>// 從labels數組中删除最後一個元素</code>
<code> </code><code>nextY -= 50; </code><code>// 控制nextY的值減50</code>
<code> </code><code>}</code>
上面的代碼中第一段粗體字代碼建立了應用的初始界面該初始界面隻包含兩個按鈕并且程式還為這兩個按鈕綁定了事件處理方法。
該應用的關鍵就是實作add:和remove:兩個方法其中add:方法中粗體字代碼負責建立一個UILabel控件每次建立的UILabel的Y坐标并不相同并将這個UILabel控件添加到該控制器關聯的UIView内。這樣即可實作每次使用者觸碰該按鈕程式界面就會添加一個UILabel控件而remove:方法中粗體字代碼則負責把labels數組的最後一個元素UILabel控件從父控件中删除并從該數組中删除該元素。
通過上面的程式即可實作通過使用者互動來動态添加丶删除程式界面控件。編譯丶運作該程式并多次觸碰添加丶删除按鈕後可能看到如圖9.37所示的動态界面。
9.5.3 自定義UI控件
UIView控件隻是一個矩形的空白區域并沒有任何内容。iOS應用的其他UI控件都繼承了UIView這些UI控件都是在UIView提供的空白區域上繪制外觀。
當開發者打算派生自己的UI控件時首先定義一個繼承View基類的子類然後重寫View類的一個或多個方法通常可以被使用者重寫的方法如下。
initWithFrame:前面已經見到程式建立UI控件時常常會調用該方法執行初始化是以如果你需要對UI控件執行一些額外的初始化即可通過重寫該方法來實作。
initWithCoder:程式通過在nib檔案中加載完該控件後會自動調用該方法。是以如果程式需要在nib檔案中加載該控件後執行自定義初始化則可通過重寫該方法來實作。
drawRect:如果程式需要自行繪制該控件的内容則可通過重寫該方法來實作。
layoutSubviews如果程式需要對該控件所包含的子控件布局進行更精确的控制可通過重寫該方法來實作。
didAddSubview:當該控件添加子控件完成時将會激發該方法。
willRemoveSubview:當該控件将要删除子控件時将會激發該方法。
willMoveToSuperview:當該控件将要添加到其父控件中時将會激發該方法。
didMoveToSuperview當把該控件添加到父控件完成時将會激發該方法。
willMoveToWindow: 當該控件将要添加到視窗中時将會激發該方法。
didMoveToWindow當把該控件添加到視窗完成時将會激發該方法。
touchesBegan:withEvent:當使用者手指開始觸碰該控件時将會激發該方法。
touchesMoved:withEvent:當使用者手指在該控件上移動時将會激發該方法。
touchesEnded:withEvent:當使用者手指結束觸碰該控件時将會激發該方法。
touchesCancelled:withEvent:使用者取消觸碰該控件時将會激發該方法。
當需要開發自定義View時開發者并不需要重寫上面列出的所有方法而是根據業務需要重寫上面的部分方法。例如下面的跟随手指運動的小球示例程式就隻重寫drawRect:方法。
執行個體跟随手指運動的小球
為了實作一個跟随手指運動的小球示例我們考慮開發自定義的UI控件這個UI控件将會在指定位置繪制一個小球這個位置可以動态改變。當使用者通過手指在螢幕上拖動時程式監聽到這個手指動作并把手指動作的位置傳入自定義UI控件然後通知該控件重繪即可。
首先建立一個Single View Application然後通過該應用的項目導航面闆打開Main.storyboard檔案選中Dock區内唯一場景内的View Controller節點或選中界面布局檔案中的根UI控件UIView也就是界面中大塊的丶右上角有個電池圖示的白色矩形區域然後按下鍵盤上的command+option+3快捷鍵打開Xcode的身份檢查器通過身份檢查器可以看到該界面布局檔案的根UI控件的實作類是UIView如圖9.38所示。
該應用并不打算使用預設的UIView作為根控件是以将圖9.38所示對話框中Class文本框内的實作類改為FKCustomView這表明程式将使用FKCustomView作為界面設計的根控件。
接下來程式需要開發自定義的FKCustomView類其步驟如下。
①用滑鼠右鍵單擊項目檔案夾然後單擊“New File”菜單項Xcode彈出如圖9.39所示的對話框。
圖9.38 通過身份檢查器面闆管理UI控件的實作類
圖9.39 建立objective-C類
②在圖9.39所示對話框的左邊選中iOS分類下的Cocoa Touch然後在對話框右邊選中“objective-Cclass”清單項後單擊“Next”按鈕系統顯示如圖9.40所示的對話框。
圖9.40 确定類名和父類
③在圖9.40所示的對話框中輸入類名選擇父類之後單擊“Next”按鈕Xcode将會顯示一個儲存檔案夾用于确定新建立檔案的存儲路徑。選擇合适的路徑後單擊“Create”按鈕即可建立一個新的objective-C類。
下面是自定義控件類實作部分的代碼接口部分僅僅隻是繼承UIView即可。
程式清單codes/09/9.5/CustomView/CustomView/FKCustomView.m
<code>#import "FKCustomView.h"</code>
<code>@implementation FKCustomView</code>
<code>// 定義兩個變量記錄目前觸碰點的坐标</code>
<code>int</code> <code>curX;</code>
<code>int</code> <code>curY;</code>
<code>- (</code><code>void</code><code>) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event</code>
<code> </code><code>// 擷取觸碰事件的UITouch事件</code>
<code> </code><code>UITouch *touch = [touches anyObject];</code>
<code> </code><code>// 得到觸碰事件在目前元件上的觸碰點</code>
<code> </code><code>CGPoint lastTouch = [touch locationInView:self];</code>
<code> </code><code>// 擷取觸碰點的坐标</code>
<code> </code><code>curX = lastTouch.x;</code>
<code> </code><code>curY = lastTouch.y;</code>
<code> </code><code>// 通知該元件重繪</code>
<code> </code><code>[self setNeedsDisplay];</code>
<code>// 重寫該方法來繪制該UI控件</code>
<code>- (</code><code>void</code><code>)drawRect:(CGRect)rect</code>
<code> </code><code>// 擷取繪圖上下文</code>
<code> </code><code>CGContextRef ctx = UIGraphicsGetCurrentContext();</code>
<code> </code><code>// 設定填充顔色</code>
<code> </code><code>CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);</code>
<code> </code><code>// 以觸碰點為圓心繪制一個圓形</code>
<code> </code><code>CGContextFillEllipseInRect(ctx, CGRectMake(curX - 10, curY - 10, 20, 20));</code>
上面的程式自定義了UIView的子類FKCustomView該子類重寫了drawRect:方法該方法的邏輯很簡單它僅僅隻是以觸碰點為圓心繪制一個圓形。除此之外該自定義UIView子類還重寫了touchesMoved方法每當使用者觸碰該元件時程式就會将觸碰點的坐标賦給curX丶curY兩個變量并通知該控件調用drawRect:方法來重繪自身。這樣即可保證每當使用者觸碰該控件時該控件總會在觸碰點繪制一個紅色圓形。
編譯丶運作該程式即可看到如圖9.41所示的效果。
——本文節選自《瘋狂ios講義上》