用xib创建一个可重用的UIView(并从storyboard中加载)

好的,StackOverflow上有很多关于此的帖子,但没有一篇特别清楚解决方案。 我想用附带的xib文件创建一个自定义的UIView 。 要求是:

  • 没有单独的UIViewController - 一个完全独立的类
  • 在课堂上的奥特莱斯让我设置/获取视图的属性
  • 我目前的做法是:

  • 覆盖-(id)initWithFrame:

    -(id)initWithFrame:(CGRect)frame {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:self
                                            options:nil] objectAtIndex:0];
        self.frame = frame;
        return self;
    }
    
  • 使用-(id)initWithFrame:以编程方式实例化-(id)initWithFrame:在我的视图控制器中

    MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    [self.view insertSubview:myCustomView atIndex:0];
    
  • 这工作正常(虽然从来没有调用[super init]并简单地设置对象使用加载的笔尖的内容似乎有点怀疑 - 这里有建议在这种情况下添加子视图也很好)。 但是,我希望能够从故事板实例化视图。 所以我可以:

  • 在故事板的父视图中放置一个UIView
  • 将其自定义类设置为MyCustomView
  • 覆盖-(id)initWithCoder: - 我见过的代码最常用的模式如下:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(void)initializeSubviews {
        typeof(view) view = [[[NSBundle mainBundle]
                             loadNibNamed:NSStringFromClass([self class])
                                    owner:self
                                  options:nil] objectAtIndex:0];
        [self addSubview:view];
    }
    
  • 当然,这是行不通的,因为我是否使用上面的方法,或者是否以编程方式实例化,都以递归方式调用-(id)initWithCoder:进入-(void)initializeSubviews并从文件加载nib。

    还有其他几个SO问题,比如在这里,在这里,在这里。 然而,没有给出的答案令人满意地解决了这个问题:

  • 一个常见的建议似乎是将整个类嵌入到UIViewController中,然后在那里进行nib加载,但这对我来说似乎并不理想,因为它需要添加另一个文件作为包装
  • 任何人都可以给出如何解决这个问题的建议,并获得工作网点在自定义UIView最小麻烦/没有瘦控制器包装? 还是有一种更简洁的方式来用最少的样板代码做事?


    你的问题是调用loadNibNamed: from(的后代) initWithCoder: loadNibNamed: loadNibNamed:内部调用initWithCoder: loadNibNamed: 如果你想覆盖故事板编码器,并始终加载你的xib实现,我建议采用以下技术。 将属性添加到您的视图类中,并在xib文件中将其设置为预定值(在用户定义的运行时属性中)。 现在,在调用[super initWithCoder:aDecoder]; 检查财产的价值。 如果是预定值,则不要调用[self initializeSubviews];

    所以,像这样的东西:

    -(instancetype)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
    
        if (self && self._xibProperty != 666)
        {
            //We are in the storyboard code path. Initialize from the xib.
            self = [self initializeSubviews];
    
            //Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
            //self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:@"backgroundColor"];
        }
    
        return self;
    }
    
    -(instancetype)initializeSubviews {
        id view =   [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    
        return view;
    }
    

    请注意,这个质量保证(如许多)确实只是具有历史意义。

    如今在iOS上的多年和几年,一切都只是一个容器视图。 完整教程在这里

    (事实上​​,苹果公司最近在不久前添加了Storyboard References,这使得它变得更加容易。)

    这是一个典型的故事板,无处不在的容器视图。 一切都是容器视图。 这只是你如何制作应用程序。

    在这里输入图像描述

    (作为一种好奇心,KenC的答案显示了它是如何完成的,它用来将xib加载到一种包装视图中,因为你不能真正“分配给自己”)。


    我将此作为一个单独的帖子加入,以更新Swift发布的情况。 LeoNatan描述的方法在Objective-C中完美工作。 但是,当从Swift中的xib文件加载时,更严格的编译时检查会阻止self被分配。

    因此,除了将从xib文件加载的视图作为自定义UIView子类的子视图添加外,没有别的选择,而不是完全替换自己。 这与原始问题中概述的第二种方法类似。 使用这种方法的Swift中的类的粗略概述如下:

    @IBDesignable // <- to optionally enable live rendering in IB
    class ExampleView: UIView {
    
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            initializeSubviews()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            initializeSubviews()
        }
    
        func initializeSubviews() {
            // below doesn't work as returned class name is normally in project module scope
            /*let viewName = NSStringFromClass(self.classForCoder)*/
            let viewName = "ExampleView"
            let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
                                   owner: self, options: nil)[0] as! UIView
            self.addSubview(view)
            view.frame = self.bounds
        }
    
    }
    

    这种方法的缺点是在视图层次中引入了一个额外的冗余层,在使用Objective-C中LeoNatan概述的方法时,该层不存在。 然而,这可能被认为是必要的罪恶,也是Xcode设计基本方式的产物(我仍然觉得很难将自定义的UIView类与UI布局以一致的方式联系起来在故事板和代码中) - 在初始化器中取代self批发之前,似乎从未像是一种特别可以解释的做事方式,尽管每个视图基本上有两个视图类别似乎也不那么好。

    尽管如此,这种方法的一个令人满意的结果是,我们不再需要将视图的自定义类设置为接口构建器中的类文件,以确保分配给self时的正确行为,并且因此递归调用init(coder aDecoder: NSCoder)时发布loadNibNamed()被破坏(通过不在xib文件中设置自定义类,普通香草UIView的init(coder aDecoder: NSCoder) ,而不是我们的自定义版本将被调用)。

    尽管我们不能直接对存储在xib中的视图进行类自定义,但是在将视图的文件所有者设置为我们的自定义类之后,我们仍然可以使用出口/操作等将视图链接到我们的“父”UIView子类:

    设置自定义视图的文件所有者属性

    在下面的视频中可以找到使用这种方法一步一步展示这种视图类实现的视频。

    链接地址: http://www.djcxy.com/p/87701.html

    上一篇: Creating a reusable UIView with xib (and loading from storyboard)

    下一篇: iOS Nested View Controllers view inside UIViewController's view?