【问题标题】:Reuse a uiview xib in storyboard在情节提要中重用 uiview xib
【发布时间】:2015-07-31 20:09:22
【问题描述】:

我通常喜欢在界面生成器中创建和设计我的 uiview。有时我需要在 xib 中创建一个视图,该视图可以在情节提要的多个视图控制器中重用。

【问题讨论】:

    标签: uiview autolayout interface-builder uistoryboard xib


    【解决方案1】:

    在情节提要中重用和渲染 xib。

    使用 Swift 2.2 和 Xcode 7.3.1 测试

    1 ---- 创建一个名为“DesignableXibView”的新 UIView

    • 文件 > 新建 > 文件 > 源代码 > Cocoa Touch 类 > UIView

    2 ---- 创建一个名为“DesignableXibView”的匹配xib文件

    • 文件 > 新建 > 文件 > 用户界面 > 视图

    3 ----设置xib的文件所有者

    1. 选择xib
    2. 选择文件的所有者
    3. 在身份检查器中将自定义类设置为“DesignableXibView”。

    • 注意:不要在 xib 上设置视图的自定义类。只有文件所有者!

    4 ---- DesignableXibView 的实现

    //  DesignableXibView.swift
    
    import UIKit
    
    @IBDesignable
    
    class DesignableXibView: UIView {
    
        var contentView : UIView?
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            xibSetup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            xibSetup()
        }
    
        func xibSetup() {
            contentView = loadViewFromNib()
    
            // use bounds not frame or it'll be offset
            contentView!.frame = bounds
    
            // Make the view stretch with containing view
            contentView!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
    
            // Adding custom subview on top of our view (over any custom drawing > see note below)
            addSubview(contentView!)
        }
    
        func loadViewFromNib() -> UIView! {
    
            let bundle = NSBundle(forClass: self.dynamicType)
            let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
            let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
            return view
        }
    
    }
    

    5 ---- 在故事板中测试您的可重用视图

    1. 打开故事板
    2. 添加视图
    3. 设置该视图的自定义类
    4. 等一下……轰!!

    【讨论】:

    • 谢谢!您可能想要添加一个使用 Swift 约束的示例,就像您在 Obj-C 答案中所做的那样(使用和不使用 VFL)。
    • 我回答了我自己的问题,看起来我在阅读这些 SO 文章之前设置了网点,说设置文件所有者,而不是视图。它在我断开所有插座后工作,确保文件所有者正确,然后重新添加所有插座。
    • 设置文件所有者和自定义类有什么区别?
    • 我也没有在界面生成器中看到预览。 Xcode 9
    • 代码在运行时有效,但在我的 Xcode 9 中,它在模块行下显示“可设计构建失败”
    【解决方案2】:

    NEW! updated answer with ability to render directly in the storyboard (and swift!)

    适用于 Xcode 6.3.1

    创建一个名为“ReuseableView”的新 UIView

    • 文件 > 新建 > 文件 > 源代码 > Cocoa Touch 类 > UIView

    创建一个名为“ReuseableView”的匹配 xib 文件

    • 文件 > 新建 > 文件 > 用户界面 > 视图

    设置xib的文件所有者

    1. 选择xib
    2. 选择文件的所有者
    3. 在 Identity Inspector 中将自定义类设置为“ReusableView”。

      • 注意:不要在 xib 上设置视图的自定义类。只有文件所有者!

    从 ReuseableView.xib 中的视图到您的 ReuseableView.h 界面建立一个出口

    1. 打开助手编辑器
    2. Control + 从视图拖动到您的界面

    添加 initWithCoder 实现以加载视图并添加为子视图。

    - (id)initWithCoder:(NSCoder *)aDecoder{
        self = [super initWithCoder:aDecoder];
        if (self) {
    
            // 1. load the interface
            [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
            // 2. add as subview
            [self addSubview:self.view];
            // 3. allow for autolayout
            self.view.translatesAutoresizingMaskIntoConstraints = NO;
            // 4. add constraints to span entire view
            [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
            [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
    
        }
        return self;
    }
    

    在故事板中测试可重复使用的视图

    1. 打开故事板
    2. 添加视图
    3. 设置该视图的自定义类

    跑步观察!

    【讨论】:

    • @Garfbargle 在此之上添加...是否可以在情节提要中显示 xib 的子元素?在你的最后一步中,添加的视图看起来像一个灰色的正方形,有什么办法可以解决吗?谢谢!
    • @andres.cianio 是的,但是您必须采取完全不同的方法。您必须以编程方式构建视图并使用 IBDesignable 指令。这是专门为那些想要在视觉上构建视图的人设计的。我不知道如何在 xib 中直观地构建视图并将其呈现在情节提要中。如果这很快成为可能,我不会感到惊讶,但如果还没有的话。
    • @Garfbargle 谢谢...这救了我的命,但是在插入视图时让它在 SB 中渲染会很漂亮;)
    • 很好的解释!但我有一个提示:使用UINib(nibName: nibName, bundle: nil).instantiateWithOwner(nil, options: nil),它比 NSBundle-Version 更快。
    • @Garfbargle 这似乎满足了您可以在 IB 中设计并在情节提要中呈现的要求。基本上,您完全按照您为 XIB 所做的工作并输入 @IBDesignable on the class. 您还必须实现 init(frame:) - 但除此之外它工作得很好! supereasyapps.com/blog/2014/12/15/…
    【解决方案3】:

    Swift 3&4 更新到已接受的答案

    1.创建一个名为“DesignableXibView”的新 UIView

    • 文件 > 新建 > 文件 > 源代码 > Cocoa Touch 类 > UIView

    2。创建一个名为“DesignableXibView”的匹配 xib 文件

    • 文件 > 新建 > 文件 > 用户界面 > 视图

    3.设置xib的文件所有者

    在身份检查器中选择“DesignableXibView.xib”>“文件的所有者”>将“自定义类”设置为“DesignableXibView”。

    • 注意:不要在xib上设置视图的自定义类。只有 文件所有者!

    4. DesignableXibView 的实现

        import UIKit
    
    @IBDesignable
    
    class DesignableXibView: UIView {
    
        var contentView : UIView!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            xibSetup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            xibSetup()
        }
    
        func xibSetup() {
            contentView = loadViewFromNib()
    
            // use bounds not frame or it'll be offset
            contentView.frame = bounds
    
            // Make the view stretch with containing view
            contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
    
            // Adding custom subview on top of our view 
            addSubview(contentView)
        }
    
        func loadViewFromNib() -> UIView! {
    
            let bundle = Bundle(for: type(of: self))
            let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
            let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
    
            return view
        }
    } 
    

    5 在故事板中测试您的可重用视图

    • 打开故事板

    • 添加视图

    • 设置该视图的自定义类

    【讨论】:

    • 我无法让它在 Xcode8/Swift3 中工作。运行应用程序时工作正常,但不显示在情节提要中。
    • 对我来说很好。如果您尝试在 xib 上查看更改,它将不会显示。在其他控制器中添加一个视图,然后将该视图的类设置为DesignableXibView。构建项目以查看更改。
    • 也为我工作。只需按照 Garfbargle 帖子的第 5 步中所述将 DesignableXibView 添加到我当前的故事板
    • 只有我在运行时收到“无法在包中加载 NIB”?
    • @Jonny 我认为String(describing: type(of: self)) 应该安全地做到这一点
    【解决方案4】:

    swift 2 中的 initWithCoder 函数,如果有人在翻译时遇到问题:

    required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            UINib(nibName: String(self.dynamicType), bundle: NSBundle.mainBundle()).instantiateWithOwner(self, options: nil)
            self.addSubview(view)
            self.view.translatesAutoresizingMaskIntoConstraints = false
            self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterY , metrics: nil, views: ["view": self.view]))
            self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterX , metrics: nil, views: ["view": self.view]))
        }
    

    【讨论】:

    • 谢谢!对于那些不想使用 VFL 的人,您可能需要添加一个不使用 VFL 的示例。
    【解决方案5】:

    对于任何试图将接受的答案(@Garfbargle)改编为 Objective-C 的人

    仅仅将Swift 转换为Objective-C 是不够的。我很难在 Storyboard 中进行实时渲染。

    翻译整个代码后,视图在设备(或模拟器)上运行时加载良好,但 Storyboard 中的实时渲染不起作用。原因是我使用了[NSBundle mainBundle] 而Interface Builder 没有访问mainBundle。您必须改用[NSBundle bundleForClass:self.classForCoder]。 BOOM,实时渲染现在可以使用了!

    注意:如果您遇到自动布局问题,请尝试在 Xib 中禁用 Safe Area Layout Guides

    为方便起见,我将整个代码留在这里,因此您只需复制/粘贴(对于所有过程,请关注original answer):

    BottomBarView.h

    #import <UIKit/UIKit.h>
    
    IB_DESIGNABLE
    @interface BottomBarView : UIView
    
    @end
    

    BottomBarView.m

    #import "BottomBarView.h"
    
    @interface BottomBarView() {
        UIView *contentView;
    }
    
    @end
    
    
    @implementation BottomBarView
    
    -(id) initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self xibSetup];
        }
        return self;
    }
    
    -(id) initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self xibSetup];
        }
        return self;
    }
    
    -(void) xibSetup {
        contentView = [self loadViewFromNib];
    
        contentView.frame = self.bounds;
    
        contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
        [self addSubview:contentView];
    }
    
    -(UIView*) loadViewFromNib {
        NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder]; //this is the important line for view to render in IB
        UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
        UIView *view = [nib instantiateWithOwner:self options:nil][0];
    
        return view;
    }
    
    @end
    

    如果您遇到一些问题,请告诉我,但它应该几乎可以开箱即用 :)

    【讨论】:

      【解决方案6】:

      这是您一直想要的答案。您可以只创建您的CustomView 类,将它的主实例放在带有所有子视图和插座的xib 中。然后,您可以将该类应用于情节提要或其他 xib 中的任何实例。

      无需摆弄 File's Owner,或将 outlet 连接到代理或以特殊方式修改 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

      汤姆?

      【讨论】:

      • 伙计,这是我多年来安装的最好的框架之一。非常感谢您为此付出的努力。它与 Xcode 12.5、swift 5、iOS 14 等完美配合。
      【解决方案7】:

      如果有人感兴趣,这里是 Xamarin.iOS 版本的@Garfbargle 的代码第 4 步

      public partial class CustomView : UIView
          {
              public ErrorView(IntPtr handle) : base(handle)
              {
      
              }
      
              [Export("awakeFromNib")]
              public override void AwakeFromNib()
              {
                  var nibObjects = NSBundle.MainBundle.LoadNib("CustomView", this, null);
                  var view = (UIView)Runtime.GetNSObject(nibObjects.ValueAt(0));
                  view.Frame = Bounds;
                  view.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
      
                  AddSubview(rootView);
              }
          }
      

      【讨论】:

        猜你喜欢
        • 2015-03-05
        • 2015-12-31
        • 1970-01-01
        • 2014-03-20
        • 1970-01-01
        • 1970-01-01
        • 2015-11-14
        • 1970-01-01
        • 2017-05-06
        相关资源
        最近更新 更多