为UIView自定义Xib

 

一、需求

 通过Interface Builder的形式创建Xib,并将其和一个UIView的子类绑定,如何实现?

二、解决

 这个问题通过搜索,有大量的答案,大概答案的代码如下:

  

 也就是在你的子类中,在初始化方法initWithFrame、initWithCoder中主动加载一个xib对应的类,作为子view添加到当前的view中

 这种方式,会明显产生一个问题,会产生两个相同的自定义View对象

 

 

  先不说两个View带来的危害,可能导致业务代码的冲突,最关键的是,xib中拖出的reference指向只能在loadnib返回的对象中初始化好,你自定义的对象的指向为空。

  这样太坑爹了

  这也是下面这个问题产生的原因,想通过修改initwithCoder的返回值为自己主动反序列化xib产生的对象

  

 

  这种方案我也测试了,测试代码如下:

  

#import "XibHackInit.h"

@implementation XibHackInit

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    static BOOL alreadyInitMark = NO;
    if(self = [super initWithCoder:aDecoder])
    {
        if(!alreadyInitMark)
        {
            NSString *className = NSStringFromClass([self class]);
            if([className containsString:@"."])
            {
                className = [[className componentsSeparatedByString:@"."] lastObject];
            }
            alreadyInitMark = YES;
            UIView *targetView = [[self class] loadFromNib:className];
            alreadyInitMark = NO;
            return (XibHackInit *)(targetView?targetView:self);
        }
    }
    return self;
}

+ (UIView *)loadFromNib:(NSString *)nibName
{
    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:nibName
                                                      owner:nil
                                                    options:nil];
    UIView* nibView = (UIView*)[nibViews objectAtIndex:0];
    return nibView;
}

@end

  注意initWithCoder中用了一个BOOL变量,这个变量主要是为了避免产生的递归,loadFromNib方法一调用、又会调用到initWithCoder中,简直就成了一个不可解的死bug

  上面的代码是OC实现的,为什么不用swift实现,主要是因为swift中init方法除了返回空值之外,不能返回其他值

  这么做的结果是什么呢?程序直接Crash

  

 

  有没有更优美的方案?

 这里的折中方案是,先创建一个容器View,在容器View中加载你想要的子View,这样可以充分考虑到Xib中复用的问题

 效果:

  

 

除了清晰的View层次之外,还可以在Interface Builder中进行可视化,实现的关键代码如下:

 

核心代码:

import UIKit

@IBDesignable
class XibElementContainer: UIView {

    var elementView:UIView?
    @IBInspectable var elementNibName:String?

    override func awakeFromNib() {
        super.awakeFromNib()
        xibSetup()
    }
    
    func xibSetup() {
        guard let view = loadViewFromNib() else { return }
        view.frame = bounds
        view.autoresizingMask =
            [.flexibleWidth, .flexibleHeight]
        addSubview(view)
        elementView = view
    }
    
    func loadViewFromNib() -> UIView? {

        guard let nibName = elementNibName else { return nil }
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(
            withOwner: self,
            options: nil).first as? UIView
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        xibSetup()
        elementView?.prepareForInterfaceBuilder()
    }
}

 

有两个关键字:

@IBDesignable  InterfaceBuilder 会尝试对这个类的对象进行可视化,会调用prepareForInterfaceBuilder方法

@IBInspectable InterfaceBuilder 会显示这个字段的输入框,可以在界面上面直接输入值

 

三、最终代码

  代码:https://github.com/liqiushui/CustomXibForUIView

posted @ 2018-10-12 20:52  兜兜有糖的博客  阅读(1058)  评论(0编辑  收藏  举报