繁体   English   中英

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

[英]How to embed a custom view xib in a storyboard scene?

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

在此输入图像描述

我已连接我的插座和约束设置,就像我以前在故事板中做的那样。 我将我的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;
}

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

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

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

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

这是一个更详细的解释:

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

蓝色空间基本上是一个将“保持”你的xib的UIView。

这是你的xib:

在此输入图像描述

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

这是最终的结果:

在此输入图像描述

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

您需要在自定义UIView子类中实现awakeAfterUsingCoder: . 此方法允许您将解码的对象(来自故事板)与不同的对象(来自可重用的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;
}

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

此外,如果将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;
}

这个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) } } 
  2. 在您的故事板中,添加一个UIView,它将充当Xib的容器。 给它一个XibView的类名:

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

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

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

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})

奖励积分

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

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    xibSetup()
    contentView?.prepareForInterfaceBuilder()
}

你只需要在你的IB中拖放UIView并将其插入并设置即可

yourUIViewClass  *yourView =   [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]

在此输入图像描述

  1. 添加新文件=>用户界面=> UIView
  2. 设置自定义类 - yourUIViewClass
  3. 设置恢复ID - yourUIViewClass
  4. yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

现在,您可以根据需要自定义视图。

  • 创建xib文件File > New > New File > iOS > User Interface > View
  • 创建自定义UIView类File > New > New File > iOS > Source > CocoaTouch
  • 将xib文件的标识分配给自定义视图类
  • 在视图控制器的viewDidLoad中,使用loadNibNamed:NSBundle.mainBundle上初始化xib及其关联文件,并且返回的第一个视图可以添加为loadNibNamed:的子视图。
  • 从笔尖加载的自定义视图可以保存到属性中,以便在viewDidLayoutSubviews设置框架。 只需将框架设置为self.view的框架,除非你需要使它小于self.view

     class ViewController: UIViewController { weak var customView: MyView! override func viewDidLoad() { super.viewDidLoad() self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView self.view.addSubview(customView) addButtonHandlerForCustomView() } private func addButtonHandlerForCustomView() { customView.buttonHandler = { [weak self] (sender:UIButton) in guard let welf = self else { return } welf.buttonTapped(sender) } } override func viewDidLayoutSubviews() { self.customView.frame = self.view.frame } private func buttonTapped(button:UIButton) { } } 
  • 此外,如果要从xib与UIViewController实例进行对话,请在自定义视图的类上创建弱属性。

     class MyView: UIView { var buttonHandler:((sender:UIButton)->())! @IBAction func buttonTapped(sender: UIButton) { buttonHandler(sender:sender) } } 

这是GitHub上的项目

多年来我一直在使用这段代码。 如果您计划在XIB中使用自定义类视图,请将其放在自定义类的.m文件中。

作为副作用,它会导致调用awakeFromNib,因此您可以将所有init / setup代码保留在那里。

- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
    if ([[self subviews] count] == 0) {
        UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
        view.frame = self.frame;
        view.autoresizingMask = self.autoresizingMask;
        view.alpha = self.alpha;
        view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
        return view;
    }
    return self;
}

早期回归@brandonscript的想法更加流畅一点:

override func awakeFromNib() {

    guard let xibName = xibName,
          let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
          let views = xib as? [UIView] else {
        return
    }

    if views.count > 0 {
        self.addSubview(views[0])
    }

}

虽然我不推荐您要关闭的路径,但可以通过在希望显示视图的位置放置“嵌入式视图控制器视图”来完成。

嵌入包含单个视图的视图控制器 - 您要重用的视图。

在这一段时间已经有一段时间了,我已经看到了许多答案。 我最近重新访问它,因为我刚刚使用UIViewController嵌入。 这有效,直到你想在“运行时重复的元素”(例如UICollectionViewCell或UITableViewCell)中添加一些东西。 @TomSwift提供的链接让我遵循了这种模式

A)不要让父视图类成为自定义类类型,而是让FileOwner成为目标类(在我的例子中,CycleControlsBar)

B)嵌套小部件的任何插座/动作链接都是这样的

C)在CycleControlsBar上实现这个简单的方法:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
        self.addSubview(container)
        container.constrainToSuperview() // left as an excise for the student
    }
}

像魅力一样工作,比其他方法简单得多。

这是你一直想要的答案。 您可以创建CustomView类,在xib中包含所有子视图和出口的主实例。 然后,您可以将该类应用于故事板或其他xib中的任何实例。

无需摆弄文件所有者,或将插座连接到代理或以特殊方式修改xib,或添加自定义视图的实例作为其自身的子视图。

这样做:

  1. 导入BFWControls框​​架
  2. 将您的超类从UIView更改为NibView (或从UITableViewCell更改为NibTableViewCell

而已!

它甚至可以与IBDesignable一起在故事板中的设计时渲染自定义视图(包括xib中的子视图)。

您可以在此处阅读更多相关信息: https//medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

你可以在这里获得开源的BFWControls框​​架: https//github.com/BareFeetWare/BFWControls

这里有一个驱动它的代码的简单摘录,以防你好奇: https//gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

汤姆👣

“正确”的答案是,您并不打算使用相应的笔尖制作可重复使用的视图。 如果视图子类作为可重用对象很有价值,那么它很少需要一个笔尖来使用它。 以UIKit提供的每个视图子类为例。 这种想法的一部分是一个视图子类,实际上很有价值,不会使用nib实现,这是Apple的一般视图。

通常当您在笔尖或故事板中使用视图时,您仍然希望以图形方式针对给定的用例进行调整。

您可以考虑使用“复制粘贴”来重新创建相同或类似的视图,而不是制作单独的笔尖。 我相信它可以达到相同的要求,它会让你或多或少地与苹果公司的做法保持一致。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM