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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27807951/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 04:15:24  来源:igfitidea点击:

How to embed a custom view xib in a storyboard scene?

iosobjective-cxcodeinterface-builderuistoryboard

提问by Travis Griggs

I'm relatively new in the XCode/iOS world; I've done some decent sized storyboard based apps, but I didn't ever cut me teeth on the whole nib/xib thing. I want to use the same tools for scenes to design/layout a reusable view/control. So I created my first ever xib for my view subclass and painted it up:

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

enter image description here

在此处输入图片说明

I have my outlets connected and constraints setup, just like I'm used to doing in the storyboard. I set the class of my File Ownerto that of my custom UIViewsubclass. So I assume I can instantiate this view subclass with some API, and it will configured/connected as shown.

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

Now back in my storyboard, I want to embed/reuse this. I'm doing so in a table view prototype cell:

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

enter image description here

在此处输入图片说明

I've got a view. I've set the class of it to my subclass. I've created an outlet for it so I can manipulate it.

我有一个看法。我已经将它的类设置为我的子类。我为它创建了一个出口,所以我可以操纵它。

The $64 question is where/how do I indicate that it's not enough to just put an empty/unconfigured instance of my view subclass there, but to use the .xib I created to configure/instantiate it? It would be really cool, if in XCode6, I could just enter the XIB file to use for a given UIView, but I don't see a field for doing that, so I assume I have to do something in code somewhere.

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

(I do see other questions like this on SO, but haven't found any asking for just this part of the puzzle, or up to date with XCode6/2015)

(我确实在 SO 上看到了其他类似的问题,但没有发现任何只要求这部分拼图的问题,或者是最新的 XCode6/2015)

Update

更新

I am able to get this to kind of work by implementing my table cell's awakeFromNibas follows:

我可以通过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;
}

Is this as easy as it gets then? Or am I working too hard for this?

事情就这么简单吗?还是我为此太努力了?

采纳答案by Segev

You're almost there. You need to override initWithCoder in your custom class you assigned the view to.

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

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

Once that's done the StoryBoard will know to load the xib inside that UIView.

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

Here's a more detailed explanation:

这是更详细的解释:

This is how your UIViewControllerlooks like on your story board: enter image description here

这是你UIViewController在故事板上的样子: 在此处输入图片说明

The blue space is basically a UIView that will "hold" your xib.

蓝色空间基本上是一个 UIView,它将“容纳”你的 xib。

This is your xib:

这是你的xib:

enter image description here

在此处输入图片说明

There's an Action connected to a button on it that will print some text.

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

and this is the final result:

这是最终结果:

enter image description here

在此处输入图片说明

The difference between the first clickMe and the second is that the first was added to the UIViewControllerusing the StoryBoard. The second was added using code.

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

回答by TomSwift

You need to implement awakeAfterUsingCoder:in your custom UIViewsubclass. This method allows you to exchange the decoded object (from the storyboard) with a different object (from your reusable xib), like so:

您需要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;
}

There's a pretty good writeup herediscussing this technique and a few alternatives.

有一个相当不错的书面记录在这里讨论这个技术和一些替代品。

Furthermore, if you add IB_DESIGNABLEto your @interface declaration, and provide an initWithFrame:method you can get design-time preview to work in IB (Xcode 6 required!):

此外,如果您添加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;
}

回答by brandonscript

A pretty cool and reusable way of doing this Interface Builder and Swift 4:

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

  1. Create a new class like so:

    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. In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of XibView:

  3. In the property inspector of this new XibView, set the name of your .xib (without the file extension) in the IBInspectable field:

  4. Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to XibView(ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:

  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. 在这个 new 的属性检查器中XibView,在 IBInspectable 字段中设置 .xib 的名称(不带文件扩展名):

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

One thing to note:This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkitto make things easy:

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

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

Bonus points

奖励积分

Allegedly you can use prepareForInterfaceBuilder()to make these reusable views visible in Interface Builder, but I haven't had much luck. This blogsuggests adding a contentViewproperty, and calling the following:

据称,您可以使用prepareForInterfaceBuilder()这些可重用视图在 Interface Builder 中可见,但我运气不佳。这个博客建议添加一个contentView属性,并调用以下内容:

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

回答by smileBot

  • Create xib file File > New > New File > iOS > User Interface > View
  • Create custom UIView class File > New > New File > iOS > Source > CocoaTouch
  • Assign the xib file's identity to the custom view class
  • In viewDidLoad of the view controller initialize the xib and its associated file using loadNibNamed:on NSBundle.mainBundleand the first view returned can be added as a subview of self.view.
  • The custom view loaded from the nib can be saved to a property for setting the frame in viewDidLayoutSubviews. Just set the frame to self.view's frame unless you need to make it smaller than 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) {
    
        }
    }
    
  • Also, if you want to talk back from the xib to your UIViewControllerinstance then create a weak property on the custom view's class.

    class MyView: UIView {
    
        var buttonHandler:((sender:UIButton)->())!
    
        @IBAction func buttonTapped(sender: UIButton) {
            buttonHandler(sender:sender)
        }
    }
    
  • 创建xib文件 File > New > New File > iOS > User Interface > View
  • 创建自定义 UIView 类 File > New > New File > iOS > Source > CocoaTouch
  • 将 xib 文件的身份分配给自定义视图类
  • 在视图控制器的 viewDidLoad 中使用loadNibNamed:on初始化 xib 及其关联文件,NSBundle.mainBundle返回的第一个视图可以添加为self.view的子视图。
  • 从笔尖加载的自定义视图可以保存到用于在viewDidLayoutSubviews. 只需将框架设置为self.view's 框架,除非您需要使其小于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)
        }
    }
    

Here's the project on GitHub

这是GitHub 上的项目

回答by alexgophermix

I've been using this code snippet for years. If you plan on having custom class views in your XIB just drop this in the .m file of your custom class.

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

As a side effect it results in awakeFromNib being called so you can leave all your init/setup code in there.

作为副作用,它会导致调用awakeFromNib,因此您可以将所有初始化/设置代码留在那里。

- (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;
}

回答by Yuyutsu

You just have to drag and drop UIViewin your IB and outlet it and set

你只需要UIView在你的 IB 中拖放并输出它并设置

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

enter image description here

在此处输入图片说明

Step

  1. Add New File => User Interface => UIView
  2. Set Custom Class - yourUIViewClass
  3. Set Restoration ID - yourUIViewClass
  4. 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]

Now you can customize view as you want.

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

回答by Sasha Prokhorenko

A little bit more swifty version of @brandonscript 's idea with early return:

@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])
    }

}

回答by Dustin

While I don't recommend the path you're going down it can be done by placing an "embedded view controller view" where you want the view to appear.

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

Embed a view controller that contains a single view -- the view you want to be reused.

嵌入一​​个包含单个视图的视图控制器——您想要重用的视图。

回答by Travis Griggs

It's been a while on this one, and I've seen a number of answers go by. I recently revisited it because I had just been using UIViewController embedding. Which works, until you want to put something in "an element repeated at runtime" (e.g. a UICollectionViewCell or a UITableViewCell). The link provided by @TomSwift led me to follow the pattern of

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

A) Rather than make the parent view class be the custom class type, make the FileOwner be the target class (in my example, CycleControlsBar)

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

B) Any outlet/action linking of the nested widgets goes to that

B) 嵌套小部件的任何出口/动作链接都指向那个

C) Implement this simple method on CycleControlsBar:

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
    }
}

Works like a charm and so much simpler than the other approaches.

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

回答by barefeettom

Here's the answer you've wanted all along. You can just create your CustomViewclass, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.

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

No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.

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

Just do this:

只需这样做:

  1. Import BFWControls framework
  2. Change your superclass from UIViewto NibView(or from UITableViewCellto NibTableViewCell)
  1. 导入 BFWControls 框架
  2. 从更改超UIViewNibView(或UITableViewCellNibTableViewCell

That's it!

就是这样!

It even works with IBDesignable to render your custom view (including the subviews from the xib) at design time in the storyboard.

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

You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

你可以阅读更多关于它在这里: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls

您可以在此处获取开源 BFWControls 框架:https: //github.com/BareFeetWare/BFWControls

And here's a simple extract of the code that drives it, in case you're curious: https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

这是驱动它的代码的简单摘录,以防您好奇:https: //gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom

汤姆