从Xib文件加载UIView的5种方式
在不同的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/
水平有限,翻译的不准确和有错误的还请大家指正。大家可以直接访问原文地址。
转载请注明出处
从Xib文件加载UIView的5种方式的更多相关文章
- WebView加载页面的两种方式——网络页面和本地页面
WebView加载页面的两种方式 一.加载网络页面 加载网络页面,是最简单的一种方式,只需要传入http的URL就可以,实现WebView加载网络页面 代码如下图: 二.加载本地页面 1.加载asse ...
- [Android] Android ViewPager 中加载 Fragment的两种方式 方式(二)
接上文: https://www.cnblogs.com/wukong1688/p/10693338.html Android ViewPager 中加载 Fragmenet的两种方式 方式(一) 二 ...
- [Android] Android ViewPager 中加载 Fragment的两种方式 方式(一)
Android ViewPager 中加载 Fragmenet的两种方式 一.当fragment里面的内容较少时,直接 使用fragment xml布局文件填充 文件总数 布局文件:view_one. ...
- JavaScript判断图片是否加载完成的三种方式
JavaScript判断图片是否加载完成的三种方式 有时需要获取图片的尺寸,这需要在图片加载完成以后才可以.有三种方式实现,下面一一介绍. 一.load事件 1 2 3 4 5 6 7 8 9 10 ...
- Vue加载组件、动态加载组件的几种方式
https://cn.vuejs.org/v2/guide/components.html https://cn.vuejs.org/v2/guide/components-dynamic-async ...
- VUE 动态加载组件的四种方式
动态加载组件的四种方式: 1.使用import导入组件,可以获取到组件 var name = 'system'; var myComponent =() => import('../compon ...
- java加载配置文件的三种方式
比如我们要加载db.properties文件 如图: 比如我们要加载source目录下的db.properties文件.就有以下几种方式 第一种是文件io流: public static void l ...
- mybatis 加载配置文件的两种方式
package com.atguigu.day03_mybaits.test; import java.io.IOException;import java.io.InputStream;import ...
- easyui datagrid加载数据的三种方式
1.加载本地数据 var obj = {"total":2,"rows":[{id:"1",name:"一"},{id: ...
随机推荐
- wing 5.0 注册机
输入License id 进入下一页获得request key ,输入request key 后点击生成,即可生成激活码,亲测可用 下载链接 密码:adwj
- 微型 ORM 的第二篇 DapperLambda性能测试[Dapper比较篇]
由于这周比较忙,所以本来想做的性能测试,一直没时间,想想还是今天给补上吧 由于很多人都担心性能问题,封装之后跟Dapper的性能差距是多少,今天我给出我的测试方法,仅供参考. 创建IDbConnect ...
- python 实现单链表
#! /usr/bin/env python ### ### Linked List python implementation ### ### @reference Data Structures ...
- javascript小练习—点击将DIV变成红色(通过for循环遍历)
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- Android 图片处理效果集
总共可以处理出大约100种图片效果,常见的和不常见的滤镜效果可以处理出来,可以说涵盖了市面上所有特效相机可以处理出来的效果.项目所有代码都是纯java代码实现,没有封装成jar包.截图是挑选了几张有代 ...
- Highchart 改编风力风向图
基于highchart修改而成的风力风向图 1.替换highchart.js drawpoints方法,替换内容如下: drawPoints: function() { var a, b = this ...
- 通过web远程访问服务器的ipython
如果想同过一个Web浏览器的方式远程访问服务器上的ipython notebook sever,可通过下面的步骤实现. 服务器:ubuntu14.04 server 客户端:windows/unix/ ...
- HDU 3501 Calculation 2
题目大意:求小于n的与n不互质的数的和. 题解:首先欧拉函数可以求出小于n的与n互质的数的个数,然后我们可以发现这样一个性质,当x与n互质时,n-x与n互质,那么所有小于n与n互质的数总是可以两两配对 ...
- ZOJ 1698 (最大流入门)
Power NetworkTime Limit:5000MS Memory Limit:32768KB 64bit IO Format:%lld & %llu SubmitStat ...
- 用上Google才是正事 分享几个訪问Google的IP和域名
通过VPN或者GAE等代理进行訪问,GAE下载请移步<GAE 3.1.18 最新版本号下载 用上Google才是正事>.这是大家通经常使用的办法.也有同学们不愿意使用代理软件.那今天来分享 ...