zoukankan      html  css  js  c++  java
  • 使用xib开发界面

    纯代码写界面有时候会降低开发效率,对于一些通用简单的界面,例如程序设置界面,可以使用xib进行开发。
    一、关于xib

    1. xib和nib

    xib文件可以被Xcode编译成nib文件,xib文件本质上是一个xml文件,而nib文件就是编译后的二进制文件,该文件将视图等控件对象封装了起来,而在程序运行起来后,这些对象会被激活。

    xib文件本质上是一个xml文件,可以用vim或cat命令查看,例如:

    $ cat ~/Desktop/JLN-1_xib/JLN-1_xib/GrayViewController.xib
    
    <document type="com.apple.interfacebuilder3.cocoatouch.xib" version="3.0" toolsversion="6254" systemversion="14b25" targetruntime="ios.cocoatouch" propertyaccesscontrol="none" useautolayout="yes" usetraitcollections="yes">
        
            <plugin identifier="com.apple.interfacebuilder.ibcocoatouchplugin" version="6247">
        
        
            <placeholder placeholderidentifier="ibfilesowner" id="-1" userlabel="file's owner" customclass="grayviewcontroller">
                
                    <outlet property="actionbutton" destination="edu-ds-gip" id="qav-o1-ta6">
                    <outlet property="titlelabel" destination="ycj-fh-rdg" id="xj4-yo-zzp">
                    <outlet property="view" destination="i5m-pr-fkt" id="sfx-zr-jgt">
                
            
            <placeholder placeholderidentifier="ibfirstresponder" id="-2" customclass="uiresponder">
            <view clearscontextbeforedrawing="no" contentmode="scaletofill" id="i5m-pr-fkt">
                <rect key="frame" x="0.0" y="0.0" width="300" height="44">
                <autoresizingmask key="autoresizingmask" widthsizable="yes" heightsizable="yes">
                
                    <button opaque="no" contentmode="scaletofill" fixedframe="yes" contenthorizontalalignment="center" contentverticalalignment="center" buttontype="roundedrect" linebreakmode="middletruncation" translatesautoresizingmaskintoconstraints="no" id="edu-ds-gip">
                        <rect key="frame" x="246" y="7" width="46" height="30">
                        <state key="normal" title="button">
                            <color key="titleshadowcolor" white="0.5" alpha="1" colorspace="calibratedwhite">
                        
                        
                            <action selector="action:" destination="-1" eventtype="touchupinside" id="svp-jp-gk9">
                        
                    
                    <label opaque="no" userinteractionenabled="no" contentmode="left" horizontalhuggingpriority="251" verticalhuggingpriority="251" fixedframe="yes" text="label" linebreakmode="tailtruncation" baselineadjustment="alignbaselines" adjustsfontsizetofit="no" translatesautoresizingmaskintoconstraints="no" id="ycj-fh-rdg">
                        <rect key="frame" x="129" y="11" width="42" height="21">
                        <fontdescription key="fontdescription" type="system" pointsize="17">
                        <color key="textcolor" cocoatouchsystemcolor="darktextcolor">
                        <nil key="highlightedcolor">
                    
                
                <color key="backgroundcolor" white="1" alpha="1" colorspace="custom" customcolorspace="calibratedwhite">
                <nil key="simulatedstatusbarmetrics">
                <nil key="simulatedtopbarmetrics">
                <nil key="simulatedbottombarmetrics">
                <freeformsimulatedsizemetrics key="simulateddestinationmetrics">
                <point key="canvaslocation" x="382" y="285">
            
        
    

    nib文件可以在程序的Build目录下找到。

    2. xib文件的若干属性

    xib文件有以下几个重要的属性:

    1. xib文件名

    2. File’s Owner

    3. xib文件中的视图的Class

    4. xib文件中的视图的Outlet指向

    从哪里加载xib,加载xib中的什么视图,都可以根据这几个属性得出。

    二、Demo实践

    Demo传送门

    1. 加载xib中File’s Owner为nil的视图

    BlueView.xib

    blueview_xib.png

    MainViewController.m

    ...
    @property (strong, nonatomic) UIView *blueView;
    ...
    - (void)loadBlueViewFromXIB {
        // BlueView.xib的File's Owner为nil
        NSArray *views = [[NSBundle mainBundle] loadNibNamed:@"BlueView" owner:nil options:nil];
        self.blueView = views[0];
        
        // 从xib加载进来的View大小是确定的,但是该视图在父视图中的位置是不确定的
        // 此外,视图中的子视图也是原封不动地Load进来的
        CGRect rect = _blueView.frame;
        rect.origin.x += 37.5f;
        rect.origin.y += 80.0f;
        _blueView.frame = rect;
        [self.view addSubview:_blueView];
    }

    运行结果:

    302.jpg

    结论:

    • File’s Owner为nil的xib文件中的视图属于通用视图,在工程中可以复用

    • 从xib加载进来的View大小是确定的,但是该视图在父视图中的位置是不确定的,因此需要开发者自行指定

    • 视图中的所有子视图会被原封不动地Load进来

    2. 加载xib中File’s Owner为self的视图

    greenview_xib.png

    MainViewController.m

    ...
    @property (weak, nonatomic) IBOutlet UIView *greenView;
    ...
    - (void)loadGreenViewFromXIB {
        // GreenView.xib的File's Owner设为self,并建立了一个从该xib的View到self的IBOutlet greenView
        [[NSBundle mainBundle] loadNibNamed:@"GreenView" owner:self options:nil];
        
        // 只要self主动调用Load XIB的方法,self持有的IBOutlet指向的视图就会被初始化
        // 这里不需要通过views[0]的方式存取视图
        CGRect rect = _greenView.frame;
        rect.origin.x = _blueView.frame.origin.x;
        rect.origin.y = _blueView.frame.origin.y + 80.0f;
        _greenView.frame = rect;
        [self.view addSubview:_greenView];
    }

    运行结果:

    greenview.png

    结论:

    • File’s Owner不为nil的xib文件中的视图属于专用视图,在工程中不应该被复用

    • 只要self主动调用loadNibNamed:owner:options:方法,self持有的IBOutlet指向的视图就会被初始化

    • 存取xib中的视图不用views[0]的方式,而是通过IBOutlet类型的property进行存取

    3. 加载xib中File’s Owner为特定类的视图

    RedView.xib

    redview_xib.png

    RedViewOwner.h

    @interface RedViewOwner : NSObject
    @property (strong, nonatomic) IBOutlet UIView *redView;
    @end
    MainViewController.m
    ...
    @property (strong, nonatomic) RedViewOwner *redViewOwner;
    ...
    - (void)loadRedViewFromXIB {
        // RedView.xib的File's Owner是RedViewOwner类的实例,并建立了一个从该xib的View到RedViewOwner实例的IBOutlet
        // 只要通过_redViewOwner主动调用Load XIB的方法,该IBOutlet指向的视图就会被初始化
        self.redViewOwner = [RedViewOwner new];
        [[NSBundle mainBundle] loadNibNamed:@"RedView" owner:_redViewOwner options:nil];
        
        UIView *redView = _redViewOwner.redView;
        CGRect rect = redView.frame;
        rect.origin.x = _greenView.frame.origin.x;
        rect.origin.y = _greenView.frame.origin.y + 80.0f;
        redView.frame = rect;
        [self.view addSubview:redView];
    }

    运行结果:

    45.jpg

    结论:

    • File’s Owner类可以封装视图中的各种逻辑,而不仅仅是提供视图内容

    • 只要通过File’s Owner类主动调用loadNibNamed:owner:options:方法,该IBOutlet指向的视图就会被初始化

    4. 加载xib中文件名和视图类名一致的视图(File’s Owner为nil)

    YellowView.xib

    yellowview_xib.png

    YellowView.h/m

    @interface YellowView : UIView
    + (instancetype)viewFromNIB;
    @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
    @end
    @implementation YellowView
    // Convenience Method
    + (instancetype)viewFromNIB {
        // 加载xib中的视图,其中xib文件名和本类类名必须一致
        // 这个xib文件的File's Owner必须为空
        // 这个xib文件必须只拥有一个视图,并且该视图的class为本类
        NSArray *views = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil];
        return views[0];
    }
    - (void)awakeFromNib {
        // 视图内容布局
        self.backgroundColor = [UIColor yellowColor];
        self.titleLabel.textColor = [UIColor whiteColor];
    }
    @end
    MainViewController.m
    ...
    @property (strong, nonatomic) YellowView *yellowView;
    ...
    - (void)loadYellowViewFromXIB {
        // 说明见YellowView.m的viewFromNIB方法
        self.yellowView = [YellowView viewFromNIB];
        
        CGRect rect = _yellowView.frame;
        UIView *redView = _redViewOwner.redView;
        rect.origin.x = redView.frame.origin.x;
        rect.origin.y = redView.frame.origin.y + 80.0f;
        _yellowView.frame = rect;
        [self.view addSubview:_yellowView];
    }

    运行结果:

    46.jpg

    结论:

    这里的viewFromNib方法只是对loadNibNamed:owner:options:方法的一个简单封装,要求的条件包括: - xib文件名和本类类名必须一致 - 这个xib文件的File’s Owner必须为空 - 这个xib文件必须只拥有一个视图,并且该视图的class为本类

    5. 通过UIViewController的initWithNibName:bundle:方法加载xib文件中的视图

    BlackView.xib

    blackview_xib_custom.png

    如果BlackViewController类希望self.view就是xib文件中的View,可以在Connections页中建立view -> File’s Owner的Outlet,如下:

    blackview_xib_connections.png

    BlackViewController.h/m

    @interface BlackViewController : UIViewController
    @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
    // Convenience Method
    + (instancetype)viewControllerFromNIB;
    @end
    @implementation BlackViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor blackColor];
        self.titleLabel.textColor = [UIColor whiteColor];
    }
    + (instancetype)viewControllerFromNIB {
        return [[BlackViewController alloc] initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
    }
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    @end

    MainViewController.m

    ...
    @property (strong, nonatomic) BlackViewController *blackViewController;
    ...
    - (void)loadBlackViewFromXIB {
        self.blackViewController = [[BlackViewController alloc] initWithNibName:@"BlackViewController" bundle:[NSBundle mainBundle]];
        
        // 或使用Conveniece Method,但要求xib文件名和View Controller类名一致
        // self.blackViewController = [BlackViewController viewControllerFromNIB];
        
        UIView *blackView = _blackViewController.view;
        CGRect rect = blackView.frame;
        rect.origin.x = _yellowView.frame.origin.x;
        rect.origin.y = _yellowView.frame.origin.y + 80.0f;
        blackView.frame = rect;
        [self.view addSubview:blackView];
    }

    运行结果:

    blackview.png

    结论:

    • 将xib的File’s Owner设成一个UIViewController子类,可以将这个xib文件的视图展示和外部响应事件(例如点击一个按钮触发的点击事件,该视图的手势事件等)全部封装在一个View Controller中,如果把按钮的点击事件封装在一个UIView类中,貌似破坏了MVC模式,因此最好将xib的File’s Owner设成一个UIViewController子类,该类可以通过addChildViewController方法将其添加到现有的View Controller上。如果只是希望加载视图,可以通过viewcontroller.view存取。

    如果希望ViewControllerA加载并响应aXIBView中的按钮点击事件,这时必须建立一个aXIBView到ViewControllerA的IBAction,如果ViewControllerA需要拥有多个这样的XIB,那么ViewControllerA会变得非常的庞大,此时可以通过为每一个XIB设置一个ViewController,再让ViewControllerA加载这些Child View Controllers,这样可以将这些事件的响应职责和视图的描绘工作分派给专门的Child View Controller,在减小ViewControllerA体积的同时,也可以提高各个xib的可复用性。

    • 这里的viewControllerFromNIB方法其实就是initWithNibName:bundle:方法的一个简单封装,要求:xib的File’s Owner设为本类。

    6. 通过UIViewController+NIB加载xib文件中的View Controller类和其视图

    GrayView.xib

    QQ截图20150202102345.jpg

    UIViewController+NIB.h/m

    @interface UIViewController (NIB)
    // 要求xib文件名和View Controller类名一致
    + (instancetype)loadFromNib;
    @end
    @implementation UIViewController (NIB)
    + (instancetype)loadFromNib {
        // [self class]会由调用的类决定
        Class controllerClass = [self class];
        NSLog(@"class = %@", controllerClass);
        return [[controllerClass alloc] initWithNibName:NSStringFromClass(controllerClass) bundle:[NSBundle mainBundle]];
    }
    @end

    GrayViewController.h/m

    @interface GrayViewController : UIViewController
    @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
    @property (weak, nonatomic) IBOutlet UIButton *actionButton;
    @end
    @implementation GrayViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor grayColor];
        
        self.titleLabel.text = @"Gray View";
        self.titleLabel.textColor = [UIColor whiteColor];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.font = [UIFont systemFontOfSize:8.5f];
        
        [self.actionButton setTitle:@"action" forState:UIControlStateNormal];
        [self.actionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    }
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    // 推荐从XIB文件中加载View Controller的方法,这种方法可以将XIB文件中的视图和其按钮响应事件全部封装在GrayViewController
    // 如果GrayViewController的按钮响应事件由MainViewController作出响应,那么二者的耦合度就过高
    // 建议:
    // 单纯的通用View展示,使用从xib文件加载视图的方法,File's Owner设为nil
    // 特定拥有者的View展示,从xib文件加载视图时,File's Owner设为拥有者
    // 如果视图中有按钮响应事件,或其它可以和用户交互的事件,建议采用从XIB文件中加载View Controller的方法,这样可以封装UI展示和交互事件
    - (IBAction)action:(id)sender {
        NSLog(@"action");
    }
    @end

    MainViewController.m

    ...
    @property (strong, nonatomic) GrayViewController *grayViewController;
    ...
    - (void)loadGrayViewFromXIB {
        self.grayViewController = [GrayViewController loadFromNib];
        
        UIView *grayView = _grayViewController.view;
        UIView *blackView = _blackViewController.view;
        CGRect rect = grayView.frame;
        rect.origin.x = blackView.frame.origin.x;
        rect.origin.y = blackView.frame.origin.y + 80.0f;
        grayView.frame = rect;
        [self.view addSubview:grayView];
    }

    运行结果:

    grayview.png

    结论:

    这里我专门写了一个UIViewController+NIB的category,只需要调用loadFromNib类方法就可以加载xib中的视图。要求: - xib文件的File’s Owner必须设置为对应的View Controller类。

    三、总结

    在写界面时同时混用xib和代码可以提高效率,而对xib的使用主要体现在其专用性和通用性上。

    • 对于一些专门的界面,例如App中的设置界面,纯代码写难免会浪费时间,此时可以通过xib文件的拖控件方法来定制。这个xib是专用于某一个界面的,目的是提高效率。

    • 对于一些通用的控件甚至界面,例如一个很漂亮但实现起来非常复杂的按钮,此时可以通过load xib文件中的视图来快速添加。这个xib对于所有视图是共用的,目的是提高可复用性。

    对于通用的xib:

    • 如果xib只是单纯的界面展示,那么File’s Owner可以随意。

    • 如果xib中包含了按钮、手势等用户输入事件,那么File’s Owner最好设置为UIViewController类的子类。

    四、自问自答

    以前使用xib时一直都有点疑问,xib中可以有多个视图控件,但是从xib中load出来的是一个数组,那么怎么确定哪个对象对应的是哪个控件呢?

    可以实践一下:

    PurpleView.xib

    purpleview_xib.png

    随便在xib文件中加了几个视图。

    接下来将其load出来看看:

    MainViewController.m

    - (void)logViewsFromXIB {
        NSLog(@"%s begin", __func__);
        NSArray *views = [[NSBundle mainBundle] loadNibNamed:@"PurpleView" owner:nil options:nil];
        for (int i = 0; i < views.count; i++) {
            id obj = views[i];
            NSLog(@"%d : %@", i, [obj class]);
        }
        NSLog(@"%s end", __func__);
    }

    控制台输出如下:

    2015-01-09 15:03:06.629 JLN-1_xib[3139:121677] -[MainViewController logViewsFromXIB] begin
    2015-01-09 15:03:06.635 JLN-1_xib[3139:121677] 0 : UIView
    2015-01-09 15:03:06.635 JLN-1_xib[3139:121677] 1 : UIButton
    2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] 2 : UITableView
    2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] 3 : UILabel
    2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] -[MainViewController logViewsFromXIB] end

    结论:

    从xib中load出来的views数组中视图对象的排列顺序和xib scene中的对象排列顺序一致(其实就是xml文件中元素的排序而已)。如下:

    purpleview_xib_scene.png

    可以将其打乱并重新运行程序查看结果。

    参考资料:http://www.ifun.cc/blog/2014/02/22/ioskai-fa-zhi-xibji-qiao-jie-shao/

  • 相关阅读:
    NSDate的处理:前一天、后一天等关于时区偏移的处理以及在数据库中的使用
    《powershell 的版本号所引起的载入 FSharp 编译器问题》基本解决
    hdu 2055 An easy problem (java)
    昨天登陆页面,无法进入后台,今天攻克了
    关于ubuntu下qt编译显示Cannot connect creator comm socket /tmp/qt_temp.xxx/stub-socket的解决的方法
    (转)Struts2的拦截器
    (转)Struts2的标签库
    (转)OGNL与值栈
    (转)Struts2访问Servlet的API及......
    (转)Struts2快速入门
  • 原文地址:https://www.cnblogs.com/kings0secret-cn/p/5191475.html
Copyright © 2011-2022 走看看