zoukankan      html  css  js  c++  java
  • xib的动态桥接

    本文原文发表自我的【自建博客】,cnblogs同步发表,格式未经调整,内容以原博客为准

    我是前言

    个人很主张使用Interface Builder(以下都简称IB)来构建程序UI,包括storyboardxib,相比代码更可视和易于修改,尤其在使用AutoLayout的时候,一目了然。
    但用了这么久IB之后发现一个很大的槽点,就是IB间很难嵌套混用,比如一个xib中的view是另一个xib的子view,或者一个storyboard中两个vc都用到了一个xib构建的view等。解决方法一般是代码手动拼接,这就造成了比较混乱的情况。
    本文将尝试解决这个问题,实现xib的动态桥接,并提供一个支持cocoapods的开源工具类供方便使用。

    一张图顶十句话:

    实现效果:


    黑魔法方法

    实现这个功能的关键在于:在ib加载的某个时刻将placeholder的view动态替换成从xib加载的view,下面的方法就可以做到:

    1
    
    - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER;
    

    这个方法很少用到,在NSObject (NSCoderMethods)中定义,由NSCoder在decode过程中调用(于-initWithCoder:之后),所以说就算从文件里decode出来的对象也会走这个方法。
    方法后面有NS_REPLACES_RECEIVER这个宏:

    1
    
    #define NS_REPLACES_RECEIVER __attribute__((ns_consumes_self)) NS_RETURNS_RETAINED
    


    在clang的文档中可以找到对这个编译器属性的介绍

    > One use of this attribute is declare your own init-like methods that do not follow the standard Cocoa naming conventions.

    所以这个宏主要为了给编译器标识出这个方法可以像self = [super init]一样使用,并作出合理的内存管理。
    So,这个方法提供了一个机会,可以将decode出来的对象替换成另一个对象


    动态桥接流程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
    {
        self = [super awakeAfterUsingCoder:aDecoder];
        
        // 0. 判断是否要进行替换
        // 1. 根据self.class从xib创建真正的view
        // 2. 将placeholder的属性、autolayout等替换到真正的view上
        // 3. return 真正的view
    }
    

    流程不难理解,就是有2个小难点:

    • 步骤1从xib创建真正的view时也会调用这个方法,会造成递归,如何判断
    • 迁移AutoLayoutConstrains

    解决递归问题

    这个topic全网可能就《这篇文章》有写,本文也是从它发起的,但是发现它的方法并不能解决所有问题(尤其是用storyboard加载xib时),所以换了个思路,采取了设置标志位的方式避免递归调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
    {
        self = [super awakeAfterUsingCoder:aDecoder];
        if (这个类的Loading标志位 -> NO)
        {
            Loading标志位 -> YES
            从xib加载真实的View (这里会递归调用这个函数)
            return 真实View
        }
        Loading标志位 -> NO
        return self
    }
    

    方法有点土,但是有效了,源代码文章后面会给地址。

    迁移AutoLayoutConstrains

    由于IB在加载AutoLayoutConstrains时的顺序是先加载子View内部的约束,后加载父View上的约束,而我们替换placeholder的时机是:

    1. placehodler view被创建(只带width,height的自身约束)
    2. 真正的view被从xib动态加载(带其子view的所有约束)
    3. placeholder被替换成真的view
    4. placeholder view在其父View(一直到父父父…View)的约束被创建

    所以说,迁移AutoLayout时,只需要把placeholder view的自身约束copy到真实View上就好了(停顿10s感受下)
    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    - (void)replaceAutolayoutConstrainsFromView:(UIView *)placeholderView toView:(UIView *)realView
    {
        for (NSLayoutConstraint *constraint in placeholderView.constraints)
        {
            NSLayoutConstraint* newConstraint  = [NSLayoutConstraint constraintWithItem:realView
                                                         attribute:constraint.firstAttribute
                                                         relatedBy:constraint.relation
                                                            toItem:nil // Only first item
                                                         attribute:constraint.secondAttribute
                                                        multiplier:constraint.multiplier
                                                          constant:constraint.constant];
            newConstraint.shouldBeArchived = constraint.shouldBeArchived;
            newConstraint.priority = constraint.priority;
            [realView addConstraint:newConstraint];
        }
    }
    


    One more thing,保证AutoLayout生效还要加上下面这句话:

    1
    
    realView.translatesAutoresizingMaskIntoConstraints = NO;
    

    开源项目XXNibBridge

    光说方案不给源码还是不地道的,demo放到了我的github上面的XXNibBridge项目,回顾一下上面的关系图:

    不得不提到IB命名约定的最佳实践方案:

    将类名作为Cell或者VC的Reusable Identifier
    ReuseIdentifier一直比较蛋疼,我一般将Cell的类名作为ReuseIdentifier(当然,大多数情况我们都会子类化Cell的),写法如:

    1
    
    [self.tableView registerClass:[XXSarkCell class] forCellReuseIdentifier:NSStringFromClass([XXSarkCell class])];
    


    dequeueCell的时候同理,这样的好处在于省去了起名的恶心、通过ReuseId可以直接找到Cell类、同时重构Cell类名时ReuseId也不用去再改。

    View的xib与View的类名同名 同理

    实现了桥接Xib的功能的同时,也简单实现了这个命名约定:

    1
    2
    3
    4
    5
    
    // XXNibBridge.h
    + (NSString *)xx_nibID; // 类名
    + (UINib *)xx_nib; // 返回类名对应nib
    + (id)xx_loadFromNib; // 对应nib的类对象
    + (id/*UIViewController*/)xx_loadFromStoryboardNamed:(NSString *)name; // 返回类名对应的vc
    


    所以之后的代码可以这么写:

    1
    
    [tableView registerNib:[XXSarkView xx_nib] forCellReuseIdentifier:[XXSarkView xx_nibID]];
    

    XXNibBridge的使用

    Cocoapods安装

    1
    
    pod 'XXNibBridge', :git => 'https://github.com/sunnyxx/XXNibBridge.git'
    

    对于要支持Bridge的类,重载下面的方法:

    1
    2
    3
    4
    5
    6
    7
    
    #import "XXNibBridge.h"
    @implementation XXDogeView
    + (BOOL)xx_shouldApplyNibBridging
    {
        return YES;
    }
    @end
    

    在父的Xib或Storyboard中拖个UIView进来作为Placeholder,设置为真实Nib的类

    保证真实Nib的类名和Nib名相同,记得在Nib中设好Class

    Done.


    References

    http://blog.yangmeyer.de/blog/2012/07/09/an-update-on-nested-nib-loading
    http://stackoverflow.com/questions/19816703/replacing-nsview-while-keeping-autolayout-constraints
    http://clang-analyzer.llvm.org/annotations.html#attr_ns_consumes_self


    原创文章,转载请注明源地址,blog.sunnyxx.com

  • 相关阅读:
    递归函数及Java范例
    笔记本的硬盘坏了
    “References to generic type List should be parameterized”
    配置管理软件(configuration management software)介绍
    WinCE文件目录定制及内存调整
    使用Silverlight for Embedded开发绚丽的界面(3)
    wince国际化语言支持
    Eclipse IDE for Java EE Developers 与Eclipse Classic 区别
    WinCE Heartbeat Message的实现
    使用Silverlight for Embedded开发绚丽的界面(2)
  • 原文地址:https://www.cnblogs.com/sunnyxx/p/3824968.html
Copyright © 2011-2022 走看看