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

239 iphone cocoa-touch objective-c interface-builder ios

我正在尝试做一些精心设计的事情,但这应该是可行的.所以这里对你所有的专家都是一个挑战(这个论坛是你们很多人的一部分:)).

我正在创建一个问卷调查"组件",我想在NavigationContoller(我的QuestionManagerViewController)上加载."组件"是"空" UIViewController,可以根据需要回答的问题加载不同的视图.

我这样做的方式是:

  1. 创建Question1View对象作为UIView子类,定义一些IBOutlets.
  2. 创建(使用Interface Builder)Question1View.xib (这里可能是我的问题所在).我同时设置UIViewControllerUIView为类Question1View的.
  3. 我将出口与视图的组件链接(使用IB).
  4. 我重写initWithNibQuestionManagerViewController的看起来像这样:

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

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

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

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

ave*_*dev 224

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

1)创建一个自定义View子类,其中包含您希望以后可以访问的任何出口. - 我的看法

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

在MyViewController中(一个UIViewController子类)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;
Run Code Online (Sandbox Code Playgroud)

(别忘了合成它并在你的.m文件中发布它)

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

4)现在将文件的所有者插座myViewFromNib连接到笔尖中的主视图.

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

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];
Run Code Online (Sandbox Code Playgroud)

现在,只要您这样做,调用您的属性"self.myViewFromNib"将允许您从笔尖访问视图!

  • 它适用于主控制器视图(self.view)吗?由于某些原因,我需要在标准控制器init方法之后从nib加载主视图.原因 - 复杂的子类结构 (4认同)
  • 仅当您要在单个视图控制器中使用自定义笔尖时,此方法才有效。如果要在不同的视图控制器上使用它,则每个视图控制器都会动态创建自定义笔尖的实例,这将无法正常工作。 (2认同)

小智 120

谢谢你们.我确实找到了一种方法来做我想做的事.

  1. UIViewIBOutlet你需要的东西创造你的.
  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;
    
    Run Code Online (Sandbox Code Playgroud)

而已!

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

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

  • 这是正确的答案,但是,应该修改代码,使其循环遍历nibViews并对每个对象进行类检查,这样您就可以获得正确的对象.objectAtIndex:1是一个危险的假设. (42认同)
  • 我有一个非常类似的问题 - 我只是想从xib文件加载UIView.这些步骤对我不起作用 - 在将加载的视图添加为子视图后,应用程序很快崩溃并出现"访问不良". (2认同)
  • 对于iPhone 3.0,使用objectAtIndex:0来获取第一个元素.这完全像Justicle所描述的那样崩溃了.知道为什么吗? (2认同)
  • Jasconius是对的.你应该循环遍历nibViews数组,如下所示:https://gist.github.com/769539 (2认同)

Dan*_*ark 69

我不确定一些答案是在谈论什么,但我需要在下次搜索谷歌时提出这个答案.关键字:"如何从笔尖加载UIView"或"如何从NSBundle加载UIView".

这是几乎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];
} 
Run Code Online (Sandbox Code Playgroud)

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


类别: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
Run Code Online (Sandbox Code Playgroud)

快速扩展

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
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用一个例子:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果使用`isKindOf`,则可以防止Bundle结构更改. (2认同)

Fra*_*amo 22

对于那些需要管理自定义视图的多个实例(即Outlet Collection)的人,我以这种方式合并并自定义@Gonso,@ AveryDev和@Olie答案:

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

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

  3. 将您设置UIViewController为自定义视图XIB的 " 文件所有者 " ; 在此输入图像描述

  4. 在为您想要的每个实例UIViewController添加一个新UIViewsMyView,并将它们连接到UIViewController创建Outlet集合:这些视图将充当自定义视图实例的"包装"视图; 在此输入图像描述

  5. 最后,在viewDidLoadUIViewController添加以下行:

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..
Run Code Online (Sandbox Code Playgroud)


thg*_*hgc 18

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

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];
Run Code Online (Sandbox Code Playgroud)

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


Mar*_*c W 11

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

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

如果您使用a UIViewController作为视图的文件所有者,则可以使用initWithNibName:bundle:它来加载它并获取视图控制器对象.在IB中,确保view使用xib中的界面将插座设置为视图.如果你使用的是其他类型的对象作为文件的拥有者,你需要使用NSBundleloadNibNamed:owner:options:方法加载笔尖,通过文件拥有者的一个实例的方法.其所有属性将根据您在IB中定义的出口进行正确设置.


小智 10

您还可以使用UIViewController的initWithNibName而不是loadNibNamed.我发现它更简单.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC
Run Code Online (Sandbox Code Playgroud)

现在您只需要创建MySubView.xib和MySubView.h/m.在MySubView.xib中,将File的Owner类设置为UIViewController,并将类查看为MySubView.

您可以subview使用父xib文件定位和调整大小.


Oli*_*lie 8

这是一个很好的问题(+1)并且答案几乎是有帮助的;)对不起,伙计们,但我有一点时间在这里,虽然Gonso和AVeryDev提供了很好的提示.希望这个答案可以帮助别人.

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

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

  • 在MyVC.xib中,创建一个类型MySubView合适的视图,并将其放置在您想要的位置.
  • 在MyVC.h中,有

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
    
    Run Code Online (Sandbox Code Playgroud)
  • 在MyVC.m中,@synthesize mySubView;不要忘记将其发布dealloc.

  • 在MySubview.h中,有一个出口/属性UIView *view(可能没必要,但为我工作.)在.m中合成并释放它
  • 在MySubview.xib中
    • 将文件所有者类型设置为MySubview,并将视图属性链接到您的视图.
    • 布置所有位并IBOutlet根据需要连接到
  • 回到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];
    
    Run Code Online (Sandbox Code Playgroud)

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

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

如果有一种方法可以mySubview直接从它自己的xib加载,那就是摇滚,但这让我得到了我需要的位置.


sko*_*kot 8

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

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];
Run Code Online (Sandbox Code Playgroud)

这是该类别的代码(它与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
Run Code Online (Sandbox Code Playgroud)


Bry*_*jar 6

我也想做类似的事情,这就是我发现的:(SDK 3.1.3)

我有一个视图控制器A(它本身由一个导航控制器拥有),它按下按钮上的VC B:

在AViewController.m中

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];
Run Code Online (Sandbox Code Playgroud)

现在VC B的界面来自Bnib,但是当按下一个按钮时,我想进入一个"编辑模式",它有一个与不同笔尖分开的UI,但是我不希望新的VC用于编辑模式,我希望新的笔尖与我现有的B VC相关联.

那么,在BViewController.m中(按钮按下方法)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];
Run Code Online (Sandbox Code Playgroud)

然后按另一个按钮(退出编辑模式):

[editView removeFromSuperview];
Run Code Online (Sandbox Code Playgroud)

我回到原来的Bnib.

这工作正常,但请注意我的EditMode.nib中只有一个顶级obj,一个UIView obj.此笔尖中的文件所有者是否设置为BViewController或默认NSObject并不重要,但请确保文件所有者中的视图插座未设置为任何内容.如果是,那么我得到一个exc_bad_access崩溃,xcode继续加载6677堆栈帧,显示一个内部UIView方法重复调用...所以看起来像一个无限循环.(但是,我的原始Bnib中设置了View Outlet)

希望这可以帮助.


Log*_*gan 6

我做了一个我喜欢的类别:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end
Run Code Online (Sandbox Code Playgroud)

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end
Run Code Online (Sandbox Code Playgroud)

然后,这样打电话:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];
Run Code Online (Sandbox Code Playgroud)

如果您的笔尖的名称不是您班级的名称,请使用笔尖名称.

要在子类中覆盖它以获取其他行为,它可能如下所示:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
Run Code Online (Sandbox Code Playgroud)


kal*_*fun 6

在迅速

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

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

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


And*_*rea 5

对于具有可选项的Swift用户:

  1. 创建一个自定义UIView子类和一个xib文件,我们将以我们自己的类名命名:在我们的例子MemeView中。在Meme View类中,请记住@IBDesignable在类声明之前使用属性将其定义为可设计的
  2. 通过UIViewIndetity Inspector面板中的自定义子类,Rember可以在xib中设置文件的所有者

    在此处输入图片说明

  3. 现在,在xib文件中,我们可以构建界面,进行约束,创建出口,操作等。 在此处输入图片说明

  4. 我们需要为自定义类实现一些方法以在初始化后打开xib

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }
    
    Run Code Online (Sandbox Code Playgroud)

    }

  5. 在我们的自定义类中,我们还可以定义一些无法检查的属性,以从界面生成器完全控制它们

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!
    
    Run Code Online (Sandbox Code Playgroud)

}

几个例子:
在此处输入图片说明 在此处输入图片说明

如果我们需要添加更多信息,而视图是在情节提要板或其他xib中显示的,则可以实现prepareForInterfaceBuilder()此方法,只有在界面构建器中打开文件时,才执行此方法。如果您做了我写的所有事情,但没有任何效果,这是通过在实现中添加断点来调试sigle视图的一种方法。 在此处输入图片说明
这是视图层次结构。 查看hiearchy

希望可以帮助您在此处下载完整示例