01
May
從程式碼土法練鋼刻UI
Facebook

使用Interface Builder 的storyboard 或xib 來設計UI 是簡單的,直覺的,愉快的,一丁點沒有寫程式的感覺。仿佛我們已經變身為藝術家,不再是埋守程式的科學怪人。然而,如此方法能做到的功能其實有限,在很多情況下,我們還是需要利用Objective-C,一行一行地寫程式碼來雕刻出想要的UI 畫面。這一期的專欄,彼得潘將介紹基本的程式碼設計UI 觀念。學會了此基本概念後,未來我們所設計的App 畫面將不再受限於Interface Builder,而是有著無限的可能和天馬行空的想像空間!

撰文:彼得潘

 

Interface Builder 設計UI 的缺陷

透過Xcode 裡強大的UI 設計工具Interface Builder,我們可以像小時候使用小畫家軟體般,輕輕鬆鬆地設計出App 美美的畫面。一行程式碼都不用寫,就可以製作出有著生動UI 的App 畫面。但是使用Interface Builder 來設計UI,卻有著以下幾個天生的缺陷:

1. 只能設計固定的靜態畫面

無法設計動畫,也沒辦法讓畫面有動態的變化,比方設定畫面在白天顯示小天使的照片,晚上顯示小惡魔的照片。

2. 只能設計畫面的最初樣貌

我們在Interface Builder 畫布上只能設計一開始呈現的畫面。無法設計過了一段時間後,某個按鈕按下後,或是某個事件發生後畫面變成另一個模樣。

3. 無法繪製圖形

從程式我們可以繪製千變萬化的圖形,但是InterfaceBuilder 卻只能加入UI 元件,設定UI 元件的屬性,沒有畫畫的天份。

4. 不適合設計複雜的畫面

過於複雜的畫面,比方畫面上有幾十個UI 元件,此時要從Interface Builder 的畫布上擺放好每一個元件,反倒成了件難度頗高的事情。

5. 不適合遊戲

Interface Builder 上只能擺放SDK 內建的UI 元件。遊戲App 的畫面大部分都是自製手繪風,而不是採用內建的UI 元件。

 

controller 管理的view

iOS SDK 採用MVC(model - view - controller)的概念來設計App。每一頁我們眼睛所見的App 畫面其實都是view,而view 的背後其實都有一個主人,也就是所謂的controller 負責控制管理view。接下來我們實際建立一個簡單的Single View Application 專案,並且勾選storyboard,看看如何從程式裡存取controller 所管理的view。

首先讓我們瞧一眼storyboard。從storyboard 我們可以看到起始畫面controller 所管理的view,預設是一片純白,等待著我們揮灑創意,發揮埋沒多年的藝術家天份。切換到controller 的Connection Inspector 頁面後可以觀察到controller 的Outlet view(Outlets 區塊裡)連結到了畫布上的UIView 元件,正因為這條連線,產生了以下2 點重大的影響:

1. 這裡所謂controller 的view,即是controller 所管理的view。而controller 管理的view 正是我們所看到的App 畫面。正因為view 連結了畫布上的UIView 元件,所以我們在UIView 元件上所設計的樣子,將成為App啟動後呈現的模樣。

2. 由於controller 的view 連結了UIView 元件,所以到時候我們可以透過程式存取UIView 元件,改變呈現的畫面。因此我們只要經由控制起始畫 面的controller,即可調整起始畫面的樣貌。但是問題來了,此controller的類別是什麼呢?答案就藏在controller 的Identity Inspector 頁面。此頁面的Class 欄位設定了元件的類別,因此從圖中我們可以得知此controller 的類別為ViewController。

接著, 就讓我們切換到ViewController.m, 修改ViewController 的viewDidLoad method,在畫面還未真正現身前,偷偷地改變畫面吧。

ViewController.m

- (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@"self.view %@", self.view);

self.view.backgroundColor = [UIColor greenColor];

}

controller 管理的view 其實正是宣告於UIViewController 裡的view property。

UIViewController.h

@property(nonatomic,retain) UIView *view;

由於ViewController 繼承自UIViewController,所以它也同樣有著view property。從剛剛的storyboard,我們得知self.view 將連結到畫布上的UIView 元件。而當viewDidLoad 被呼叫前,此UIView 元件上的畫面早已一一被建立,並且連結到controller 上相對應的outlet。所以此時印出的self.view 將是UIView 元件的記憶體位址。

UIView 類別定義了最基本的UI 元件該有的特性,同時也是iOS SDK裡所有UI 元件的共同祖先。UIView 元件在畫面上顯示時最常見的模樣則是某個顏色的長方形樣貌。平常我們在Library Pane 裡所看到的View 元件,類別正是UIView。如圖所示,當我們點選它時,說明頁面明白地告訴我們它的類別是UIView(在名稱View 的下方)。

設定UIView 元件的backgroundColor property,將它的顏色設為綠色。

UIView.h

@property(nonatomic,copy) UIColor *backgroundColor;

 

執行結果

果然畫面變為綠色了。因此self.view 果然連結到我們當初於storyboard 上建立的UIView 元件。

從程式動態建立UI 元件

剛剛我們只是稍微地牛刀小試, 修改原先就在storyboard 上建立的UIView 元件。接下來讓我們進一步地直接從程式碼產生新的UI 元件,讓它現身在畫面上吧。

ViewController.m

- (void)viewDidLoad

{

[super viewDidLoad];

UIView *blueView = [[UIView alloc] init];

blueView.backgroundColor = [UIColor blueColor];

[self.view addSubview:blueView];

}

建立最基本的UI 元件UIView。

設定長方形的左上角座標為(10, 10),寬高為(100, 100)。將blueView 加到controller 管理的view,self.view 身上。此method的宣告如下:

UIView.h

- (void)addSubview:(UIView *)view;

addSubView: 是我們利用程式碼配置UI 時,最常呼叫的method。它的目的在於將另一個UI 元件加在自己的身上。由於所有的UI 元件皆繼承自UIView,所以每個UI 元件都可以呼叫addSubview:,換句話說,每個都擁有將另一個UI 元件加到自己身上的能力。另外有一點需注意的,當我們將view A 加到view B 上時,代表view A正被使用,正被需要著,因此view A 的retainCount 將被加1。

 

執行結果

令人心碎的,畫面上什麼都沒有,只有白茫茫的一片。藍色的UIView 哪兒去了呢?

UI 元件的大小和位置

大小和位置,是iOS UI 設計的2 大基本概念。iOS SDK提供了許多UI 元件,但是它們都有共同的2 個屬性,大小和位置。剛剛我們雖然建立了藍色的UIView,並且把它加到self.view 上。但是它卻消失無蹤,其實原因很簡單,因此我們沒有指定它的大小和位置,所以它不知道它該顯示在哪個位置,也不知它該佔據多大的空間。

但是要怎麼定義大小和位置呢? 答案是長方形。iOSSDK 利用長方形來定義一個UI 元件的大小和位置,如右圖所示,即便UI 元件是圓形,定義其大小和位置的,仍然是長方形。

 

iOS 的座標系統

長方形可以定義一個物件的大小很容易明瞭,因為長方形等於一個限制,物件一定要被包在長方形裡,不能超出邊界。但是為什麼長方形可以定義位置呢? 這得先從iOS 的座標系統說起。

iOS 的畫面有2 種,直的畫面或是橫的畫面。但不管哪種,座標的起始點都是從左上角開始,換句話說,左上角是(0,0),愈向右X 的數值愈大,愈向下Y 的數值愈大。值得注意的,為了程式上方便設計retina 和非retina 2 種不同解析度,此處的座標單位是point,而非pixel。當我們定義了圓形物件的長方形左上角頂點為(30,60) 時,即等於將此圓形物件置於距離左邊邊界30point,距離上方邊界60 point。

CGRect, CGSize, CGPoint

在iOS SDK 裡,CGRect 定義了長方形。

 

CGGeometry.h

struct CGRect {

CGPoint origin;

CGSize size;

};

typedef struct CGRect CGRect;

在CGRect 裡,origin 定義了長方形左上角的頂點。而一個點是由x,y 決定的。所以有了CGPoint 這個資料型態。

CGGeometry.h

struct CGPoint {

CGFloat x;

CGFloat y;

};

typedef struct CGPoint CGPoint;

注意x, y 的型別為CGFloat,它其實就是我們平常熟悉oat 型別。別忘了此處的單位是point。在CGRect 裡,size 定義了長方形的大小。長方形的大小,是由寬和高決定。所以有了CGSize 這個資料形態。

CGGeometry.h

struct CGSize {

CGFloat width;

CGFloat height;

};

typedef struct CGSize CGSize;

設定blueView 的大小和位置

ViewController.m

- (void)viewDidLoad

{

[super viewDidLoad];

CGRect frame = CGRectMake(10, 10, 100, 100);

UIView *blueView = [[UIView alloc] initWithFrame:frame];

blueView.backgroundColor = [UIColor blueColor];

[self.view addSubview:blueView];

}

設定長方形的左上角座標為(10, 10),寬高為(100, 100)。將blueView 加到controller 管理的view,self.view 身上。此method的宣告如下:以剛剛設定的長方形初始化blueView 的大小和位置。此method 的宣告如下:

UIView.h

- (id)initWithFrame:(CGRect)frame;

frame 參數正是前面提到定義長方形的CGRect 型別。

 

執行結果

藍色的UIView 終於現身了, 距離上邊界和左邊界都是10points,寬高也如預期的為100 points。