如何在故事板场景中嵌入自定义视图xib?

Tra*_*ggs 44 xcode objective-c interface-builder ios uistoryboard

我在XCode/iOS世界中相对较新; 我已经做了一些像样的大小基于故事板的应用程序,但我从来没有在整个nib/xib的东西上削减我的力量.我想使用相同的工具为场景设计/布局可重用的视图/控件.所以我为我的视图子类创建了我的第一个xib并绘制了它:

在此输入图像描述

我已连接我的插座和约束设置,就像我以前在故事板中做的那样.我将my的类设置File Owner为我的自定义UIView子类.所以我假设我可以用一些API实例化这个视图子类,它将如图所示配置/连接.

现在回到我的故事板中,我想嵌入/重用它.我在表视图原型单元格中这样做:

在此输入图像描述

我有一个观点.我已将它的类设置为我的子类.我为它创建了一个插座,所以我可以操纵它.

64美元的问题是在哪里/如何表明仅仅放置我的视图子类的空/未配置实例是不够的,但是使用我创建的.xib来配置/实例化它?这真的很酷,如果在XCode6中,我可以输入XIB文件用于给定的UIView,但我没有看到这样做的字段,所以我假设我必须在代码中的某些地方做一些事情.

(我确实在SO上看到了这样的其他问题,但是没有找到任何关于这部分难题的问题,或者最新的XCode6/2015)

更新

我可以通过实现我的表格单元格来实现这一点,awakeFromNib如下所示:

- (void)awakeFromNib
{
    // gather all of the constraints pointing to the uncofigured instance
    NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
        return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
    }]];
    // fetch the fleshed out variant
    ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
    // ape the current placeholder's frame
    fromXIB.frame = self.progressControl.frame;
    // now swap them
    [UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
    // recreate all of the constraints, but for the new guy
    for (NSLayoutConstraint *each in progressConstraints) {
        id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
        id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
        [self.contentView addConstraint: constraint];
    }
    // update our outlet
    self.progressControl = fromXIB;
}
Run Code Online (Sandbox Code Playgroud)

这很容易吗?还是我为此努力工作?

Seg*_*gev 28

你快到了.您需要在分配视图的自定义类中覆盖initWithCoder.

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
    }
    return self; }
Run Code Online (Sandbox Code Playgroud)

一旦完成,StoryBoard将知道在UIView中加载xib.

这是一个更详细的解释:

这就是UIViewController你的故事板上的样子: 在此输入图像描述

蓝色空间基本上是一个将"保持"你的xib的UIView.

这是你的xib:

在此输入图像描述

有一个Action连接到它上面的一个按钮,它将打印一些文本.

这是最终的结果:

在此输入图像描述

第一个clickMe和第二个之间的区别在于第一个被添加到UIViewController使用了StoryBoard.第二个是使用代码添加的.


Tom*_*ift 16

您需要awakeAfterUsingCoder:在自定义UIView子类中实现.此方法允许您将解码的对象(来自故事板)与不同的对象(来自可重用的xib)交换,如下所示:

- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
    // without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
    // set the view tag in the MyView xib to be -999 and anything else in the storyboard.
    if ( self.tag == -999 )
    {
        return self;
    }

    // make sure your custom view is the first object in the nib
    MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];

    // copy properties forward from the storyboard-decoded object (self)
    v.frame = self.frame;
    v.autoresizingMask = self.autoresizingMask;
    v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
    v.tag = self.tag;

    // copy any other attribtues you want to set in the storyboard

    // possibly copy any child constraints for width/height

    return v;
}
Run Code Online (Sandbox Code Playgroud)

这里有一篇非常好的文章,讨论了这种技术和一些替代方案.

此外,如果你添加IB_DESIGNABLE到@interface声明,并提供一个initWithFrame:方法,你可以让设计时预览在IB中工作(需要Xcode 6!):

IB_DESIGNABLE @interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [[[UINib nibWithNibName: @"MyView"
                            bundle: [NSBundle bundleForClass: [MyView class]]]

             instantiateWithOwner: nil
             options: nil] firstObject];

    self.frame = frame;

    return self;
}
Run Code Online (Sandbox Code Playgroud)

  • 这绝对比“使用第二个包装视图并将您的真实视图添加为 initWithCoder 中的子视图”更可取。任何重上下文移植都可以在类别/扩展方法中完成。删除一些配置的一个建议:大概您在 xib 中有子视图,否则将毫无意义,因此人们可能依赖子视图是否为空而不是标签属性。 (2认同)

bra*_*ipt 15

这个Interface Builder和Swift 4的一种非常酷且可重用的方式:

  1. 像这样创建一个新类:

    import Foundation
    import UIKit
    
    @IBDesignable class XibView: UIView {
    
        @IBInspectable var xibName: String?
    
        override func awakeFromNib() { 
            guard let name = self.xibName, 
                  let xib = Bundle.main.loadNibNamed(name, owner: self), 
                  let view = xib.first as? UIView else { return }    
            self.addSubview(view)
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在您的故事板中,添加一个UIView,它将充当Xib的容器.给它一个类名XibView:

  3. 在此new的属性检查器中XibView,在IBInspectable字段中设置.xib的名称(不带文件扩展名):

  4. 将新的Xib视图添加到项目中,并在属性检查器中,将Xib的"文件所有者"设置为XibView(确保您只将"文件所有者"设置为自定义类,不要将内容视图子类化,否则它将崩溃),再次设置IBInspectable字段:

有一点需要注意:这假设您将.xib框架与其容器匹配.如果你没有,或者需要它可以调整大小,你需要添加一些编程约束或修改子视图的框架以适应.我使用使事情变得简单:

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})
Run Code Online (Sandbox Code Playgroud)

奖励积分

据称,您可以使用prepareForInterfaceBuilder()这些可重用的视图在Interface Builder中可见,但我没有太多运气.此博客建议添加contentView属性,并调用以下内容:

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    xibSetup()
    contentView?.prepareForInterfaceBuilder()
}
Run Code Online (Sandbox Code Playgroud)