ios 如何使用用 Interface Builder 创建的 nib 文件加载 UIView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/863321/
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-30 15:55:50  来源:igfitidea点击:

How to load a UIView using a nib file created with Interface Builder

iphoneobjective-cioscocoa-touchinterface-builder

提问by

I'm trying to do something a bit elaborate, but something that should be possible. So here is a challenge for all you experts out there (this forum is a pack of a lot of you guys :) ).

我正在尝试做一些复杂的事情,但应该是可能的。所以这是对所有专家的挑战(这个论坛有很多人:))。

I'm creating a Questionnaire "component", which I want to load on a NavigationContoller(my QuestionManagerViewController). The "component" is an "empty" UIViewController, which can load different views depending on the question that needs to be answered.

我正在创建一个问卷“组件”,我想将其加载到NavigationContoller(我的QuestionManagerViewController)上。“组件”是一个“空” UIViewController,它可以根据需要回答的问题加载不同的视图。

The way I'm doing it is:

我这样做的方式是:

  1. Create Question1View object as a UIViewsubclass, defining some IBOutlets.
  2. Create (using Interface Builder) the Question1View.xib(HERE IS WHERE MY PROBLEM PROBABLY IS). I set both the UIViewControllerand the UIViewto be of class Question1View.
  3. I link the outlets with the view's component (using IB).
  4. I override the initWithNibof my QuestionManagerViewControllerto look like this:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    
  1. 创建 Question1View 对象作为UIView子类,定义一些IBOutlets.
  2. 创建(使用Interface Builder)Question1View.xib(这里是我的问题可能在哪里)。我将 theUIViewController和 theUIView都设置为 Question1View 类。
  3. 我将插座与视图的组件链接起来(使用 IB)。
  4. 我覆盖了initWithNibQuestionManagerViewController的看起来像这样:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

When I run the code, I'm getting this error:

当我运行代码时,我收到此错误:

2009-05-14 15:05:37.152 iMobiDines[17148:20b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:]loaded the "Question1View" nib but the view outlet was not set.'

2009-05-14 15:05:37.152 iMobiDines[17148:20b] *** 由于未捕获的异常“ NSInternalInconsistencyException”而终止应用程序,原因:“-[UIViewController _loadViewFromNibNamed:bundle:]加载了“Question1View”笔尖但未设置视图插座。

I'm sure there is a way to load the view using the nib file, without needing to create a viewController class.

我确信有一种方法可以使用 nib 文件加载视图,而无需创建 viewController 类。

回答by averydev

There is also an easier way to access the view instead of dealing with the nib as an array.

还有一种更简单的方法来访问视图,而不是将笔尖作为数组处理。

1) Create a custom View subclass with any outlets that you want to have access to later. --MyView

1) 创建一个自定义 View 子类,其中包含您以后想要访问的任何插座。 - 我的看法

2) in the UIViewController that you want to load and handle the nib, create an IBOutlet property that will hold the loaded nib's view, for instance

2) 在要加载和处理笔尖的 UIViewController 中,创建一个 IBOutlet 属性来保存已加载笔尖的视图,例如

in MyViewController (a UIViewController subclass)

在 MyViewController(一个 UIViewController 子类)中

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(dont forget to synthesize it and release it in your .m file)

(不要忘记合成它并在您的 .m 文件中发布它)

3)open your nib (we'll call it 'myViewNib.xib') in IB, set you file's Owner to MyViewController

3)在IB中打开你的笔尖(我们称之为'myViewNib.xib'),将你的文件所有者设置为MyViewController

4)now connect your file's Owner outlet myViewFromNib to the main view in the nib.

4)现在将文件的所有者出口 myViewFromNib 连接到笔尖中的主视图。

5)Now in MyViewController, write the following line:

5)现在在 MyViewController 中,写入以下行:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Now as soon as you do that, calling your property "self.myViewFromNib" will give you access to the view from your nib!

现在只要你这样做,调用你的属性“self.myViewFromNib”就可以让你从你的笔尖访问视图!

回答by averydev

Thank you all. I did find a way to do what I wanted.

谢谢你们。我确实找到了做我想做的事情的方法。

  1. Create your UIViewwith the IBOutlets you need.
  2. Create the xib in IB, design it to you liking and link it like this: The File's Owner is of class UIViewController(No custom subclass, but the "real" one). The File Owner's view is connected to the main view and its class is declared as the one from step 1).
  3. Connect your controls with the IBOutlets.
  4. The DynamicViewControllercan run its logic to decide what view/xib to load. Once its made the decission, in the loadViewmethod put something like this:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;
    
  1. UIViewIBOutlet你需要的s创建你的。
  2. 在 IB 中创建 xib,根据您的喜好设计它并像这样链接它:文件的所有者属于类UIViewController(没有自定义子类,而是“真实的”子类)。文件所有者的视图连接到主视图,其类被声明为步骤 1) 中的类。
  3. 将您的控件与IBOutlets连接。
  4. DynamicViewController可以运行它的逻辑来决定哪些查看/厦门国际银行加载。一旦做出决定,在loadView方法中放置如下内容:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;
    

That's it!

就是这样!

The main bundle's loadNibNamedmethod will take care of initializing the view and creating the connections.

主包的loadNibNamed方法将负责初始化视图和创建连接。

Now the ViewController can display a view or another depending on the data in memory, and the "parent" screen doesn't need to bother with this logic.

现在 ViewController 可以根据内存中的数据显示一个视图或另一个视图,“父”屏幕不需要打扰这个逻辑。

回答by Dan Rosenstark

I'm not sure what some of the answers are talking about, but I need to put this answer here for when I search in Google next time. Keywords: "How to load a UIView from a nib" or "How to load a UIView from an NSBundle."

我不确定某些答案在谈论什么,但是下次我在 Google 中搜索时,我需要将这个答案放在这里。关键词:“如何从笔尖加载 UIView”或“如何从 NSBundle 加载 UIView”。

Here's the code almost 100% straight up from the Apress Beginning iPhone 3book (page 247, "Using The New Table View Cell"):

这里的代码几乎 100% 直接来自 Apress Beginning iPhone 3一书(第 247 页,“使用新的表格视图单元格”):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

This supposes you have a UIViewsubclass called Blah, a nib called Blahwhich contains a UIViewwhich has its class set to Blah.

这假设您有一个UIView名为的子类Blah,一个名为的笔尖Blah,其中包含UIView其类设置为 的 a Blah



Category: NSObject+LoadFromNib

类别:NSObject+LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end


Swift Extension

Swift 扩展

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

And an example in use:

和一个使用示例:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}

回答by Francesco Vadicamo

For all those that need to manage more than one instance of the custom view, that is an Outlet Collection, I merged and customized the @Gonso, @AVeryDev and @Olie answers in this way:

对于所有需要管理多个自定义视图实例(即Outlet Collection )的用户,我以这种方式合并并自定义了 @Gonso、@AVeryDev 和 @Olie 答案:

  1. Create a custom MyView : UIViewand set it as "Custom Class" of the root UIViewin the desired XIB; custom class

  2. Create all outletsyou need in MyView(do it now because after point 3 the IB will propose you to connect outlets to the UIViewControllerand not to the custom view as we want); custom class outlet

  3. Set your UIViewControlleras "File's Owner" of the custom view XIB; enter image description here

  4. In the UIViewControlleradd a new UIViewsfor each instance of MyViewyou want, and connect them to UIViewControllercreating an Outlet Collection: these views will act as "wrapper" views for the custom view instances; enter image description here

  5. Finally, in the viewDidLoadof your UIViewControlleradd the following lines:

  1. 创建一个自定义MyView : UIView并将其设置为所需XIB 中根的“自定义类” ; UIView自定义类

  2. 创建您需要的所有插座MyView(现在就做,因为在第 3 点之后,IB 会建议您将插座连接UIViewController到自定义视图而不是我们想要的自定义视图); 自定义类插座

  3. 将您设置UIViewController为自定义视图XIB 的文件所有者” ; 在此处输入图片说明

  4. 在为您想要的每个实例UIViewController添加一个新UIViewsMyView,并将它们连接到UIViewController创建一个Outlet Collection:这些视图将充当自定义视图实例的“包装器”视图; 在此处输入图片说明

  5. 最后,在viewDidLoad您的 中UIViewController添加以下几行:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..

回答by thgc

I would use UINib to instantiate a custom UIView to be reused

我会使用 UINIb 来实例化一个自定义的 UIView 以供重用

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Files needed in this case are MyCustomView.xib, MyCustomViewClass.hand MyCustomViewClass.mNote that [UINib instantiateWithOwner]returns an array, so you should use the element which reflects the UIView you want to re-use. In this case it's the first element.

在这种情况下需要的文件是MyCustomView.xibMyCustomViewClass.hMyCustomViewClass.m注意[UINib instantiateWithOwner]返回一个数组,所以你应该使用反映你想要重用的 UIView 的元素。在这种情况下,它是第一个元素。

回答by Marc W

You should not be setting the class of your view controller to be a subclass of UIViewin Interface Builder. That is most definitely at least part of your problem. Leave that as either UIViewController, some subclass of it, or some other custom class you have.

您不应该将视图控制器的类设置UIView为 Interface Builder 中的子类。这绝对是您问题的至少一部分。将其保留为UIViewController、它的某个子类或您拥有的其他一些自定义类。

As for loading only a view from a xib, I was under the assumption that you hadto have some sort of view controller (even if it doesn't extend UIViewController, which may be too heavyweight for your needs) set as the File's Owner in Interface Builder if you want to use it to define your interface. I did a little research to confirm this as well. This is because otherwise there would be no way to access any of the interface elements in the UIView, nor would there be a way to have your own methods in code be triggered by events.

至于仅从 xib 加载视图,我假设您必须将某种视图控制器(即使它不扩展UIViewController,这对于您的需求来说可能太重量级)设置为界面中的文件所有者Builder 如果你想用它来定义你的界面。我也做了一些研究来证实这一点。这是因为否则将无法访问 中的任何界面元素UIView,也无法让您自己的代码方法由事件触发。

If you use a UIViewControlleras your File's Owner for your views, you can just use initWithNibName:bundle:to load it and get the view controller object back. In IB, make sure you set the viewoutlet to the view with your interface in the xib. If you use some other type of object as your File's Owner, you'll need to use NSBundle's loadNibNamed:owner:options:method to load the nib, passing an instance of File's Owner to the method. All its properties will be set properly according to the outlets you define in IB.

如果您使用 aUIViewController作为您的视图的文件所有者,您可以使用initWithNibName:bundle:它来加载它并取回视图控制器对象。在 IB 中,确保将view插座设置为视图,并在 xib 中使用您的界面。如果您使用其他类型的对象作为文件所有者,则需要使用NSBundle'sloadNibNamed:owner:options:方法加载笔尖,将文件所有者的实例传递给该方法。它的所有属性都将根据您在 IB 中定义的出口进行正确设置。

回答by Ying

You can also use UIViewController's initWithNibName instead of loadNibNamed. It is simpler, I find.

你也可以使用 UIViewController 的 initWithNibName 而不是 loadNibNamed。我发现它更简单。

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Now you just have to create MySubView.xib and MySubView.h/m. In MySubView.xib set the File's Owner class to UIViewController and view class to MySubView.

现在你只需要创建 MySubView.xib 和 MySubView.h/m。在 MySubView.xib 中,将 File's Owner 类设置为 UIViewController,将视图类设置为 MySubView。

You can position and size of the subviewusing the parent xib file.

您可以subview使用父 xib 文件的位置和大小。

回答by Olie

This is a great question (+1) and the answers were almosthelpful ;) Sorry guys, but I had a heck of a time slogging through this, though both Gonso & AVeryDev gave good hints. Hopefully, this answer will help others.

这是一个很好的问题 (+1) 并且答案几乎很有帮助 ;) 对不起,伙计们,但我花了很多时间来解决这个问题,尽管 Gonso 和 AVeryDev 都给出了很好的提示。希望这个答案能帮助其他人。

MyVCis the view controller holding all this stuff.

MyVC是持有所有这些东西的视图控制器。

MySubviewis the view that we want to load from a xib

MySubview是我们想要从 xib 加载的视图

  • In MyVC.xib, create a view of type MySubViewthat is the right size & shape & positioned where you want it.
  • In MyVC.h, have

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
    
  • In MyVC.m, @synthesize mySubView;and don't forget to release it in dealloc.

  • In MySubview.h, have an outlet/property for UIView *view(may be unnecessary, but worked for me.) Synthesize & release it in .m
  • In MySubview.xib
    • set file owner type to MySubview, and link the view property to your view.
    • Lay out all the bits & connect to the IBOutlet's as desired
  • Back in MyVC.m, have

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];
    
  • 在 MyVC.xib 中,创建一个类型的视图,该视图的MySubView大小、形状和放置在您想要的位置。
  • 在 MyVC.h 中,有

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
    
  • 在 MyVC.m 中,@synthesize mySubView;不要忘记在dealloc.

  • 在 MySubview.h 中,有一个出口/属性UIView *view(可能是不必要的,但对我有用。)在 .m 中合成并发布它
  • 在 MySubview.xib 中
    • 将文件所有者类型设置为MySubview,并将视图属性链接到您的视图。
    • 布置所有位并IBOutlet根据需要连接到's
  • 回到 MyVC.m,有

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];
    

The tricky bit for me was: the hints in the other answers loaded my view from the xib, but did NOT replace the view in MyVC (duh!) -- I had to swap that out on my own.

对我来说棘手的一点是:其他答案中的提示从 xib 加载了我的视图,但没有替换 MyVC 中的视图(废话!)——我不得不自己换掉它。

Also, to get access to mySubview's methods, the viewproperty in the .xib file must be set to MySubview. Otherwise, it comes back as a plain-old UIView.

此外,要访问mySubview的方法,view.xib 文件中的属性必须设置为MySubview。否则,它会作为一个普通的UIView.

If there's a way to load mySubviewdirectly from its own xib, that'd rock, but this got me where I needed to be.

如果有一种方法可以mySubview直接从它自己的 xib 加载,那就太棒了,但这让我到达了我需要的地方。

回答by skot

This is something that ought to be easier. I ended up extending UIViewControllerand adding a loadNib:inPlaceholder:selector. Now I can say

这是应该更容易的事情。我最终扩展UIViewController并添加了一个loadNib:inPlaceholder:选择器。现在我可以说

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Here's the code for the category (it does the same rigamarole as described by Gonso):

这是该类别的代码(它执行与 Gonso 描述的相同的 rigamarole):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end

回答by kalafun

In swift

在迅速

Actually my resolution to this problem was, to load the view in a viewDidLoad in my CustonViewController where I wanted to use the view like that:

实际上,我对这个问题的解决方案是,在我的 CustonViewController 中的 viewDidLoad 中加载视图,我想在其中使用这样的视图:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Don't load the view in a loadView()method! The loadView method serves for loading the view for your custom ViewController.

不要在loadView()方法中加载视图!loadView 方法用于加载自定义 ViewController 的视图。