在不同的Xib文件中最容易維護的是定義的視圖,因此對於從Xib文件中加載UIView來說一個方便的流程是非常重要。
在過去的幾年里我發現唯一易於管理創建和維護視圖(或者任何界面元素,通常會更多)方式就是從Xib實例化UIView.在界面編輯器里面創建和設計界面遠遠比使用代碼來寫界面布局和定義布局常量(尺寸、顏色)甚至一些糟糕的魔法數字來限制元素更加直觀。
現在介紹一下我在不同情況下使用過的5種方法
1、簡單方式(從Xib加載UIView比較原始的方法)
這種方式僅僅適用於只有一個視圖並且沒有任何其他交互綁定。除了對Cocoa的初學階段比較有容易理解之外,這種方式真的沒有什么特別的優勢
首先使用[NSBundle loadNibNamed:owner:options]方法,只帶第一個參數。
只要把以下代碼放到你控制器(Controller)的 implementation塊里面;
// Instantiate the nib content without any reference to it. NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:@"EPPZPlainView" owner:nil options:nil]; // Find the view among nib contents (not too hard assuming there is only one view in it). UIView *plainView = [nibContents lastObject]; // Some hardcoded layout. CGSize padding = (CGSize){ 22.0, 22.0 }; plainView.frame = (CGRect){padding.width, padding.height, plainView.frame.size}; // Add to the view hierarchy (thus retain). [self.view addSubview:plainView];
在界面編輯器(Interface builder)里面你不需要做任何特別的設置,除了你想在你的控制器里面實例化的單個定義的視圖
不需要綁定,甚至不需要指定文件的所屬類(File's owner class),不過你需要自己些在代碼里面寫布局代碼;
如圖,在界面編輯器里面,你不需要設置其他東西,只需要一個有靜態內容的View
2、引用方式(更加明確一點)
這種方式跟上有方式比相當於是上一種方式的更進一步,我們需要定義一個明確的應用來對應一個View. 有一點比較麻煩的地方就是你需要在你的控制器類里面定義一個視圖鏈接屬性來跟你的視圖鏈接起來。這主要是使這個方法太具體化,或者可以說是移植性差
@interface EPPZViewController () // Define an outlet for the custom view. @property (nonatomic, weak) IBOutlet UIView *referencedView; // An action that triggers showing the view. -(IBAction)showReferencedView; @end @implementation EPPZViewController -(IBAction)showReferencedView { // Instantiate a referenced view (assuming outlet has hooked up in XIB). [[NSBundle mainBundle] loadNibNamed:@"EPPZReferencedView" owner:self options:nil]; // Controller's outlet has been bound during nib loading, so we can access view trough the outlet. [self.view addSubview:self.referencedView]; } @end
上面這段代碼是指,你可以在界面編輯器里面定義一個上下文view(實際上是一個包裹器,或者說是一個容器)。這對於在XIB文件里面定義一個上下有關聯的布局視圖來說真的非常有用(比使用代碼布局方便多了)。但同時你需要知道界面編輯器的設置。File's Owner這里必須設置為控制器的實例並且Outlets里面的referencedView這里必須要跟一個你的視圖(View)關聯在一起。
你可以看到圖里面,File's Owner的Class屬性那里已經設置成控制器類(EPPZViewController) 並且referencedView 那里已經綁定到了一個你想要的視圖(View)
注意,不要把視圖控制器跟包裹視圖(相當於視圖根容器)連起來(即使你覺得這樣是對的,也不要這么做)。因為那會重新分配控制器的視圖在實例化這個空視圖的時候。
這種方式通過添加一個UITableViewCell到xib文件,也適用於UITableViewCell實例方法(不需要包裹視圖). 不過這個不在本次的討論范圍之內了。
3、關聯動作(實際上是在上一步基礎上增加一個代碼)
在上面基礎上,你可以很容地關聯定義的視圖里面對象發出的動作到控制器。這非常有用,雖然這一定要視圖去根一個指定類型的控制器組合在一起。因此,你僅僅需要定義一個IBAction 在主控制器里面,代碼如下
@interface EPPZViewController () @property (nonatomic, weak) IBOutlet UIView *referencedView; -(IBAction)showConnectedActionsView; -(IBAction)connectedActionsViewTouchedUp:(UIButton*) button; @end @implementation EPPZViewController -(IBAction)showConnectedActionsView { // Instantiate a referenced view (assuming outlet has hooked up in XIB). [[NSBundle mainBundle] loadNibNamed:@"EPPZConnectedActionsView" owner:self options:nil]; // Controller's outlet has been bound during nib loading, so we can access view trough the outlet. [self.view addSubview:self.referencedView]; } -(IBAction)connectedActionsViewTouchedUp:(UIButton*) button { // Any interaction (I simply remove the custom view here). [button.superview removeFromSuperview]; } @end
然后簡單的把一個按鈕事件關聯到一個你定義好的動作
4、封裝實現(這一步開始寫控制器代碼)
在這個過程控制器的代碼開始變得復雜。
當你要加入一些新的功能的時候,你控制器里面的代碼立馬就開始增多,雖然你很努力的去避免。保持客戶端端代碼簡潔的一種方式就是定義一個定制視圖的子類。然后開始把功能功能定義成接口,在子類實現
第一個技巧就是刪除那個File's Owner 依賴。,然后定義一個類EPPZSubclassedViewOwner, 定義這個類的唯一目的就是為了正確的在XIB文件中引用視圖。
這甚至不需要為這個這個定制的視圖創建一個獨立的文件,它只需要在控制器的頭部定義好接口
@class EPPZSubclassedView; @interface EPPZSubclassedViewOwner : NSObject @property (nonatomic, weak) IBOutlet EPPZSubclassedView *subclassedView; @end @interface EPPZSubclassedView : UIView +(void)presentInViewController:(UIViewController*) viewController; -(IBAction)dismiss; @end
這樣做的好處就是,我們可以定義一個接口繼承UIView,聲明presentInViewController方法。如果你需要不同的xib文件,比如對iPhone和iPad使用不同的接口,你可以把接口寫在這里,來替代在控制器里面寫滿亂七八的代碼。
此外,視圖的dismiss方法在這里也可以移到這里來,使他在自己的控制器里面不做任何事情。 在實現里面我可以適當的處理全部實現邏輯,你可以看到以下代碼:
@implementation EPPZSubclassedViewOwner @end @implementation EPPZSubclassedView +(void)presentInViewController:(UIViewController*) viewController { // Instantiating encapsulated here. EPPZSubclassedViewOwner *owner = [EPPZSubclassedViewOwner new]; [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:owner options:nil]; // Add to the view hierarchy (thus retain). [viewController.view addSubview:owner.subclassedView]; } -(IBAction)dismiss { [self removeFromSuperview]; } @end
在XIB文件里面你需要設置適當的類引用。如上,File's Owner的Class設置為EPPZSubclassedViewOwner,視圖控件的Class屬性設置EPPZSubclassedView
關聯視圖到他的引用
像按鈕事件關聯到動作一樣,關聯定義視圖的IBAction,如上圖。
通過以上的處理方式,你可以看到客戶端的代碼非常簡潔。比起不用自定義視圖關聯屬性到控制要好的很多很多。
@interface EPPZViewController -(IBAction)showSubclassedView; @end @implementation EPPZViewController -(IBAction)showSubclassedView { // A tiny one-liner that has anything to do with the custom view. [EPPZSubclassedView presentInViewController:self]; } @end
這樣看起來已經像是可以復用的代碼了,但是我們還需要在視圖(view)到控制器(controller)之間增加一些鏈接
5、封裝任何東西 (一個真正可以伸縮、可復用的方式從xib文件里面加載你定義的視圖)
上面我們成功地從控制器里面分離出視圖,我們繼續按照這種方法更好的處理動作。要實現這個,我們需要定義個小小的代理協議<EPPZDecoupledViewDelegate> 來定義控制器的功能,並且保證控制器能處理視圖過來的消息。就像通常的協議一下,它只需要兩個方法:decoupledViewTouchedUp和decoupledViewDidDismiss,如下
@class EPPZDecoupledView; @interface EPPZDecoupledViewOwner : NSObject @property (nonatomic, weak) IBOutlet EPPZDecoupledView *decoupledView; @end @protocol EPPZDecoupledViewDelegate -(void)decoupledViewTouchedUp:(EPPZDecoupledView*) decoupledView; -(void)decoupledViewDidDismiss:(EPPZDecoupledView*) decoupledView; @end @interface EPPZDecoupledView : UIView // Indicate that this view should be presented only controllers those implements the delegate methods. +(void)presentInViewController:(UIViewController<EPPZDecoupledViewDelegate>*) viewController; -(IBAction)viewTouchedUp; -(IBAction)dismiss; @end
實現需要一個delegateViewController的引用給控制器,這樣它才能轉發動作。你需要告訴控制去實現代碼方法,因此你需要這樣聲明:UIViewController <EPPZDecoupledViewDelegate>.
其他的如下
@implementation EPPZDecoupledViewOwner @end @interface EPPZDecoupledView () @property (nonatomic, weak) UIViewController <EPPZDecoupledViewDelegate> *delegateViewController; @end @implementation EPPZDecoupledView +(void)presentInViewController:(UIViewController<EPPZDecoupledViewDelegate>*) viewController { // Instantiating encapsulated here. EPPZDecoupledViewOwner *owner = [EPPZDecoupledViewOwner new]; [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:owner options:nil]; // Pass in a reference of the viewController. owner.decoupledView.delegateViewController = viewController; // Add (thus retain). [viewController.view addSubview:owner.decoupledView]; } -(IBAction)viewTouchedUp { // Forward to delegate. [self.delegateViewController decoupledViewTouchedUp:self]; } -(IBAction)dismiss { [self removeFromSuperview]; // Forward to delegate. [self.delegateViewController decoupledViewDidDismiss:self]; } @end
現在,你可以創建一個完全獨立的XIB文件了,不需要它關心它的上下文。它只實例化自己,關聯動作給自己,它是可復用的,可以從任何UIViewController來實例化實現其在頭部的聲明的代理協議
動作本身在這里不會做太多事情,其他的都在控制器的代理實現方法里面做。 因此它可以通過直接地、嚴格地、正式的代理規則自定義更多的特性。
為了讓他根據有可讀性和實用性,我們可以移動一些聲明到.m文件里面。因此對於我們定義的視圖,使用者只需要關心頭部的的聲明就好了。如
@class EPPZDecoupledView; @protocol EPPZDecoupledViewDelegate -(void)decoupledViewTouchedUp:(EPPZDecoupledView*) decoupledView; -(void)decoupledViewDidDismiss:(EPPZDecoupledView*) decoupledView; @end @interface EPPZDecoupledView : UIView +(void)presentInViewController:(UIViewController<EPPZDecoupledViewDelegate>*) viewController; @end
因此,在控制器的里面只需要實現它的代理接口就好了,因此你只需要引入<EPPZDecoupledViewDelegate>
@interface EPPZViewController () <EPPZDecoupledViewDelegate> -(IBAction)showDecoupledView; @end @implementation EPPZViewController -(IBAction)showDecoupledView { [EPPZDecoupledView presentInViewController:self]; } -(void)decoupledViewTouchedUp:(EPPZDecoupledView*) decoupledView { /* Whatever feature. */ } -(void)decoupledViewDidDismiss:(EPPZDecoupledView*) decoupledView { /* Acknowledge sadly. */ } @end
GOOD。 一個漂亮的UI模塊完工了....
源代碼訪問以下地址
https://github.com/eppz/blog.UIView_from_XIB
原文:5 approach to load UIView from Xib
http://eppz.eu/blog/uiview-from-xib/
水平有限,翻譯的不准確和有錯誤的還請大家指正。大家可以直接訪問原文地址。
轉載請注明出處