ios – 使用UINib加载xib文件实现UITableViewCell
xib文件的实质是xml,描述界面对象,每个对象都有一个很重要的属性,identity inspector面板中class属性,加载xib文件的时候实际上是实例化界面对象相对应的这些class。
xib文件的加载过程:
1.将xib文件从磁盘载入内存,有两种技术可以加载xib文件:NSBundle和UINib。
2.执行unarchive和initialize操作,该过程主要由NSCoding Protocol中的initWithCoder:(NSCoder *)decoder完成。
3.建立connections:Outlets和Actions。Outlets使用IBOutlet关键字标示,使用setValue:forKey:方法建立每个Outlet,所以每个Outlet的建立都会发送KVO通知。Actions使用IBAction关键字标示,替换void返回值,通过调用addTarget:action:forControlEvents:方法建立每个Action连接。注意,这里构建Outlets和Actions是有先后顺序的,先建立Outlets连接,随后建立Actions连接。因为,Actions的建立依赖之前建立的Outlets。
4.调用awakeFromNib方法,首先要调用super的awakeFromNib方法,之后可以设置一些个性化的操作,以及一些无法在设计时设定的操作。注意,awakeFromNib消息只发往在Interface Builder中指定的Custom Class,不会发送给Files's Owner,First Responder等占位对象。
之后,该对象的加载完成,可以进行各式各样的操作了。
使用NSBundle加载xib文件:
[[NSBundle mainBundle]loadNibNamed:<(NSString *)> owner:<(id)> options:<(NSDictionary *)>];
这是最常见的一种,loadNibNamed:owner:options:方法返回的是一个NSArray*,里面包含了所加载xib文件包含的界面对象(class)。
NSBundle每次都从磁盘上载入xib文件,而UINib则只是第一次从磁盘上载入xib文件,之后将xib文件缓存在内存中,每次新生成一个对象时,直接访问内存中的xib文件执行上面描述的2-4步,所以性能上会有很大的提升,并且开发者文档也建议对于那些重复使用的xib文件使用UINib 加载技术,当收到低内存警告时,会从内从中卸载xib文件,当再次访问的时候会从磁盘中载入。下面看一下UINib的定义:
NS_CLASS_AVAILABLE_IOS(4_0) @interface UINib : NSObject {
@private
id storage;
}
// If the bundle parameter is nil, the main bundle is used.
// Releases resources in response to memory pressure (e.g. memory warning), reloading from the bundle when necessary.
+ (UINib *)nibWithNibName:(NSString *)name bundle:(NSBundle *)bundleOrNil;
// If the bundle parameter is nil, the main bundle is used.
+ (UINib *)nibWithData:(NSData *)data bundle:(NSBundle *)bundleOrNil;
// Returns an array containing the top-level objects from the NIB.
// The owner and options parameters may both be nil.
// If the owner parameter is nil, connections to File's Owner are not permitted.
// Options are identical to the options specified with -[NSBundle loadNibNamed:owner:options:]
- (NSArray *)instantiateWithOwner:(id)ownerOrNil options:(NSDictionary *)optionsOrNil;
@end
前面两个方法很清楚,分别以不同的方式载入,第三个方法则是实例化(将对象创建出来)
表视图实例:
具体的细节就不说了
创建一个名为Empty的xib文件
注意看Table View Cell的class属性是TableViewCell,不是默认的UITableViewCell,TableViewCell.h如下:
@interface TableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UILabel *lb1; @property (weak, nonatomic) IBOutlet UILabel *lb2; - (IBAction)bt:(id)sender; @end
三个属性都和xib文件进行了链接,应该能看出来。
然后在UITableViewDataSource代理中分别进行如下操作:
//头文件内声明 UINib *nib; //实例化 self->nib = [UINib nibWithNibName:@"Empty" bundle:nil];
然后在来看tableView:cellForRowAtIndexPath:方法,这个方法标准的实现方法如下:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = [NSString stringWithFormat:@"Cell"]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } //config the cell return cell; }
这是使用代码的方式创建cell ,下面看使用UINib:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *identifier = @"Cell"; TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (cell == nil) { cell = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0]; } switch (indexPath.section) { case redSection: cell.lb1.text = @"lb1"; cell.lb2.text = @"lb2"; break; case blueSection: break; default: [[cell textLabel] setText:@"Unknow"]; } return cell; }
效果图如下:
以上只是个人理解,有错误之处欢迎指正。
从Xib文件加载UIView的5种方式,xib加载uiview5种
在不同的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/