從Xib檔案載入UIView的5種方式

zhYx__發表於2017-10-05

1、簡單方式(從Xib載入UIView比較原始的方法)

這種方式僅僅適用於只有一個檢視並且沒有任何其他互動繫結。除了對Cocoa的初學階段比較有容易理解之外,這種方式真的沒有什麼特別的優勢

首先使用[NSBundle loadNibNamed:owner:options]方法,只帶第一個引數。

只要把以下程式碼放到你控制器(Controller)的 implementation塊裡面;

1
2
3
4
5
6
7
8
9
10
11
12
// 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. 有一點比較麻煩的地方就是你需要在你的控制器類裡面定義一個檢視連結屬性來跟你的檢視連結起來。這主要是使這個方法太具體化,或者可以說是移植性差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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)

1
注意,不要把檢視控制器跟包裹檢視(相當於檢視根容器)連起來(即使你覺得這樣是對的,也不要這麼做)。因為那會重新分配控制器的檢視在例項化這個空檢視的時候。

  

1
這種方式通過新增一個UITableViewCell到xib檔案,也適用於UITableViewCell例項方法(不需要包裹檢視). 不過這個不在本次的討論範圍之內了。

  

 3、關聯動作(實際上是在上一步基礎上增加一個程式碼)

在上面基礎上,你可以很容地關聯定義的檢視裡面物件發出的動作到控制器。這非常有用,雖然這一定要檢視去根一個指定型別的控制器組合在一起。因此,你僅僅需要定義一個IBAction  在主控制器裡面,程式碼如下

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
@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檔案中引用檢視。

這甚至不需要為這個這個定製的檢視建立一個獨立的檔案,它只需要在控制器的頭部定義好介面

1
2
3
4
5
6
7
8
9
@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方法在這裡也可以移到這裡來,使他在自己的控制器裡面不做任何事情。 在實現裡面我可以適當的處理全部實現邏輯,你可以看到以下程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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,如上圖。

通過以上的處理方式,你可以看到客戶端的程式碼非常簡潔。比起不用自定義檢視關聯屬性到控制要好的很多很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
@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,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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>.

其他的如下

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
@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檔案裡面。因此對於我們定義的檢視,使用者只需要關心頭部的的宣告就好了。如

1
2
3
4
5
6
7
8
9
@class EPPZDecoupledView;
@protocol EPPZDecoupledViewDelegate
-(void)decoupledViewTouchedUp:(EPPZDecoupledView*) decoupledView;
-(void)decoupledViewDidDismiss:(EPPZDecoupledView*) decoupledView;
@end
  
@interface EPPZDecoupledView : UIView
+(void)presentInViewController:(UIViewController<EPPZDecoupledViewDelegate>*) viewController;
@end

  

 

因此,在控制器的裡面只需要實現它的代理介面就好了,因此你只需要引入<EPPZDecoupledViewDelegate> 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

相關文章