在不同的Xib文件中最容易维护的是定义的视图,因此对于从Xib文件中加载UIView来说一个方便的流程是非常重要。
在过去的几年里我发现唯一易于管理创建和维护视图(或者任何界面元素,通常会更多)方式就是从Xib实例化UIView.在界面编辑器里面创建和设计界面远远比使用代码来写界面布局和定义布局常量(尺寸、颜色)甚至一些糟糕的魔法数字来限制元素更加直观。
现在介绍一下我在不同情况下使用过的5种方法
1、简单方式(从Xib加载UIView比较原始的方法)
这种方式仅仅适用于只有一个视图并且没有任何其他交互绑定。除了对Cocoa的初学阶段比较有容易理解之外,这种方式真的没有什么特别的优势
首先使用[NSBundle loadNibNamed:owner:options]方法,只带第一个参数。
只要把以下代码放到你控制器(Controller)的 implementation块里面;
class="brush:objc;gutter:true;">// 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/
水平有限,翻译的不准确和有错误的还请大家指正。大家可以直接访问原文地址。
转载请注明出处