How to load a UIView using a nib file created with Interface Builder
我想做些有点复杂的事情,但应该是可能的。所以这对你们所有的专家来说都是一个挑战(这个论坛是你们很多人组成的)。
我正在创建一个调查问卷"组件",我想加载到一个
我的做法是:
我重写了我的
1 2 3 4 5 6 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) { // Custom initialization } return self; } |
当我运行代码时,我得到这个错误:
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.'
我确信有一种方法可以使用NIB文件加载视图,而不需要创建一个viewController类。
还有一种更容易访问视图的方法,而不是将NIB作为数组来处理。
1)创建一个自定义视图子类,其中包含以后要访问的任何出口。——MyView
2)在要加载和处理NIB的uiviewController中,创建一个IBoutlet属性来保存加载的NIB的视图,例如
在MyViewController(uiViewController子类)中
1 | @property (nonatomic, retain) IBOutlet UIView *myViewFromNib; |
(别忘了合成它并在.m文件中释放它)
3)在ib中打开nib(我们称之为myviewnib.xib),将文件的所有者设置为myviewcontroller。
4)现在将文件的所有者outlet myviewfromnib连接到nib中的主视图。
5)现在在MyViewController中,编写以下行:
1 | [[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil]; |
现在,一旦您这样做,调用您的财产"self.myviewfromnib"将允许您从您的nib访问视图!
谢谢大家。我确实找到了一种做我想做的事的方法。
1 2 3 4 5 6 7 | NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil]; QPickOneView* myView = [ nibViews objectAtIndex: 1]; myView.question = question; |
就是这样!
主包的
现在,视控器可以根据内存中的数据显示一个或另一个视图,而"父"屏幕不需要担心这种逻辑。
我不知道其中的一些答案是什么,但下次在谷歌搜索时,我需要把这个答案放在这里。关键词:"如何从NIB加载uiview"或"如何从nsbundle加载uiview"。
这是几乎100%直接从一本iphone 3开始的书(第247页,"使用新的表视图单元")开始的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - (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]; } |
这假设你有一个名为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #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 |
快速扩展
1 2 3 4 5 6 7 8 9 10 11 12 | 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 } } |
以及一个正在使用的示例:
1 2 3 4 5 | class SomeView: UIView { class func loadFromNib() -> SomeView? { return self.loadFromNib(withName:"SomeView") } } |
对于需要管理多个自定义视图实例(即outlet集合)的所有人,我合并并自定义了@gonso、@averydev和@olie,以这种方式回答:
创建一个自定义
创建您在
将您的
在
最后,在您的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 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..
我将使用uinib实例化要重用的自定义uiview
1 2 3 | UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil]; MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0]; [self.view addSubview:customView]; |
这种情况下需要的文件是mycustomview.xib、mycustomviewclass.h和mycustomviewclass.m请注意,
您不应该将视图控制器的类设置为接口生成器中
至于只从XIB加载视图,我假设您必须在Interface Builder中将某种类型的视图控制器(即使它不扩展
如果您使用
也可以使用uiviewController的initWithNibName而不是loadNibNamed。我发现这很简单。
1 2 3 | UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil]; [self.subview addSubview:aViewController.view]; [aViewController release]; // release the VC |
现在您只需要创建mysubview.xib和mysubview.h/m。在mysubview.xib中,将文件的owner类设置为uiviewcontroller,将view类设置为mysubview。
您可以使用父XIB文件定位和调整
这是一个很好的问题(+1),答案几乎是有用的;)抱歉,伙计们,但我花了很长时间苦苦研究这个问题,尽管冈索和艾弗里德夫都给出了很好的提示。希望这个答案能帮助其他人。
- 在myvc.xib中,创建一个
MySubview 类型的视图,该视图的大小和形状与所需位置相匹配。 在myvc.h中,有
1
2
3IBOutlet MySubview *mySubView
// ...
@property (nonatomic, retain) MySubview *mySubview;在myvc.m中,
@synthesize mySubView; ,不要忘记在dealloc 中释放它。- 在mysubview.h中,为
UIView *view 拥有一个出口/财产(可能不需要,但对我有效)。在.m中合成并发布它。 - 在mysubview.xib中
- 将文件所有者类型设置为
MySubview ,并将视图属性链接到视图。 - 根据需要布置所有位并连接到
IBOutlet 。
- 将文件所有者类型设置为
回到myvc.m,有
1
2
3
4
5
6
7
8NSArray *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];
对我来说,棘手的一点是:其他答案中的提示从XIB加载了我的视图,但没有替换myvc中的视图(duh!)--我必须自己把它换掉。
此外,要访问
如果有办法直接从它自己的XIB加载
这应该更容易些。最后我扩展了
1 | self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview]; |
这是该类别的代码(它执行与Gonso描述的相同的rigamarole):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @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 |
我做了一个我喜欢的分类:
1 2 3 4 5 | #import <UIKit/UIKit.h> @interface UIView (NibInitializer) - (instancetype)initWithNibNamed:(NSString *)nibNameOrNil; @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #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 |
然后,这样叫:
1 | MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil]; |
如果您的笔尖不是类名称,请使用笔尖名称。
要在子类中重写它以获得其他行为,它可能如下所示:
1 2 3 4 5 6 7 8 | - (instancetype)initWithNibNamed:(NSString *)nibNameOrNil { self = [super initWithNibNamed:nibNameOrNil]; if (self) { self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0; } return self; } |
在斯威夫特
实际上,我对这个问题的解决方案是,将视图加载到我的custonviewcontroller中的viewdidLoad中,在这里我想使用这样的视图:
1 | myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView |
不要在
我也想做类似的事情,这就是我发现的:(SDK 3.1.3)
我有一个视图控制器A(它本身属于导航控制器),它在按钮上加载VC B,按:
在aviewcontroller.m中
1 2 3 | BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil]; [self.navigationController pushViewController:bController animated:YES]; [bController release]; |
现在vc b有了b nib的接口,但是当按下一个按钮时,我想进入一个"编辑模式",它有一个独立的用户界面,来自不同的nib,但是我不想在编辑模式下有一个新的vc,我想新的nib与我现有的bvc相关联。
所以,在bview controller.m中(在button press method中)
1 2 3 4 | NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil]; UIView *theEditView = [nibObjects objectAtIndex:0]; self.editView = theEditView; [self.view addSubview:theEditView]; |
然后在另一个按钮上按(退出编辑模式):
1 | [editView removeFromSuperview]; |
我回到了我原来的BNIB。
这很好,但是注意我的editmode.nib只有一个顶级obj,一个uiview obj。不管文件在这个NIB中的所有者是设置为bView控制器还是默认nsObject,但要确保文件所有者中的视图出口没有设置为任何内容。如果是,那么我会得到一个exc-bad-access崩溃,xcode继续加载6677个堆栈帧。显示重复调用的内部uiview方法…所以看起来像一个无限循环。(视图插座设置在我原来的bnib中)
希望这有帮助。
对于具有可指定选项的Swift用户:
请记住在XIB中使用Indentity Inspector面板中的自定义
在XIB文件中,我们现在可以构建接口、进行约束、创建出口、操作等。
我们需要对自定义类实现一些方法,以便在初始化后打开XIB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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 } |
}
在我们的自定义类中,我们还可以定义一些可检查的属性,以便从接口生成器对它们进行完全控制。
class MemeView: XibbedView {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @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! |
}
举几个例子:
如果我们需要在故事板或其他XIB中显示视图时添加更多信息,为了实现这一点,我们可以实现
希望这有助于在这里下载完整的示例
我发现Aaron Hillegas(作者、导师、可可忍者)的博客帖子非常有启发性。即使您不采用他修改过的方法通过指定的初始值设定项加载NIB文件,您至少可以更好地理解正在进行的过程。我最近用这种方法取得了巨大的成功!
这里有一种使用swift的方法(目前在Xcode7测试版5中编写swift 2.0)。
从您在接口生成器中设置为"自定义类"的
1 2 3 4 5 6 7 8 9 | class func loadFromNib() -> RecordingFooterView? { let nib = UINib(nibName:"RecordingFooterView", bundle: nil) let nibObjects = nib.instantiateWithOwner(nil, options: nil) if nibObjects.count > 0 { let topObject = nibObjects[0] return topObject as? RecordingFooterView } return nil } |
然后你可以这样称呼它:
前面的回答没有考虑到在iPhone SDK的2.0和2.1之间发生的NIB(XIB)结构变化。用户内容现在从索引0开始,而不是从1开始。
您可以使用2.1宏,该宏适用于所有2.1及以上版本(iPhone之前有两个下划线:
1 2 3 4 5 6 7 8 9 10 | // Cited from previous example NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil]; int startIndex; #ifdef __IPHONE_2_1 startIndex = 0; #else startIndex = 1; #endif QPickOneView* myView = [ nibViews objectAtIndex: startIndex]; myView.question = question; |
我们在大多数应用中都使用类似的技术。
巴尼
我有理由做同样的事情(以编程方式从XIB文件加载视图),但我需要完全从
1 2 3 4 5 6 7 8 9 10 11 12 | + (id) initWithNibName:(NSString *)nibName withSelf:(id)myself { NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:myself options:nil]; for (id object in bundle) { if ([object isKindOfClass:[myself class]]) { return object; } } return nil; } |
然后我从我的子类"EDOCX1"〔17〕方法中调用它,如下所示:
1 2 3 4 5 6 7 8 | - (id)initWithFrame:(CGRect)frame { self = [Utilities initWithNibName:@"XIB1" withSelf:self]; if (self) { // Initialization code. } return self; } |
为了大家的兴趣张贴;如果有人看到任何问题没有这样做,请告诉我。
这些答案都不能解释如何创建这个问题的根源——独立XIB。没有"创建新的XIB文件"选项。
这样做
1 2 3 4 | 1) Choose"New File..." 2) Choose the"User Interface" category under the iOS section 3) Choose the"View" item 4) You will then be prompted to choose an iPhone or iPad format |
这可能看起来很简单,但它可以为您节省几分钟的时间,因为"xib"这个词在任何地方都不会出现。
@ AVeryDev
6)要将加载的视图附加到视图控制器的视图:
1 | [self.view addSubview:myViewFromNib]; |
可能需要将其从视图中移除,以避免内存泄漏。
为了澄清:视图控制器有几个iboutlet,其中一些连接到原始NIB文件中的项目(和往常一样),而另一些连接到加载的NIB中的项目。两个NIB具有相同的所有者类。加载的视图覆盖原始视图。
提示:将加载的NIB中主视图的不透明度设置为零,这样就不会模糊原始NIB中的项目。
花了好几个小时后,我想出了以下解决办法。按照这些步骤创建自定义
1)创建从uiview继承的类mycustomview。
2)创建名为MyCustomView的.xib。
3)更改.xib文件中uiview的类,在那里分配mycustomview类。
4)创建IBoutlets
5)在mycustomview*customview中加载.xib。使用以下示例代码。
1 2 | myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0]; [myView.description setText:@"description"]; |
Note: For those who still face issue can comment, I will provide them link
of sample project with myCustomView
最短版本:
1 | RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject]; |
我有一个命名XIB的惯例,其中的视图与视图相同。和视图控制器一样。然后,我不必用代码写出类名。我从具有相同名称的NIB文件加载uiview。
名为MyView的类的示例。
- 在Interface Builder中创建名为myview.xib的NIB文件
- 在Interface Builder中,添加一个uiview。将其类设置为MyView。根据您的心脏内容进行定制,将MyView的实例变量连接到您以后可能要访问的子视图。
在代码中,创建一个新的myview,如下所示:
myview*myview=[myview nib_viewfromnibwithowner:owner];
以下是此项的类别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @implementation UIView (nib) + (id) nib_viewFromNib { return [self nib_viewFromNibWithOwner:nil]; } + (id) nib_viewFromNibWithOwner:(id)owner { NSString *className = NSStringFromClass([self class]); NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil]; UIView *view = nil; for(UIView *v in nib) { if ([v isKindOfClass:[self class]]) { view = v; break; } } assert(view != nil &&"View for class not found in nib file"); [view nib_viewDidLoad]; return view; } // override this to do custom setup -(void)nib_viewDidLoad { } |
然后,我将按钮与我使用的控制器的操作连接起来,并使用自定义视图子类中的出口在标签上设置内容。
要以编程方式从swift 4中的NIB/XIB加载视图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // Load a view from a Nib given a placeholder view subclass // Placeholder is an instance of the view to load. Placeholder is discarded. // If no name is provided, the Nib name is the same as the subclass type name // public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T { let nib = loadNib(givenNibName, placeholder: placeholderView) return instantiateView(fromNib: nib, placeholder: placeholderView) } // Step 1: Returns a Nib // public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib { //1. Load and unarchive nib file let nibName = givenNibName ?? String(describing: type(of: placeholderView)) let nib = UINib(nibName: nibName, bundle: Bundle.main) return nib } // Step 2: Instantiate a view given a nib // public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T { //1. Get top level objects let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil) //2. Have at least one top level object guard let firstObject = topLevelObjects.first else { fatalError("\(#function): no top level objects in nib") } //3. Return instantiated view, placeholderView is not used let instantiatedView = firstObject as! T return instantiatedView } |
我最终为以下内容向uiview添加了一个类别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #import"UIViewNibLoading.h" @implementation UIView (UIViewNibLoading) + (id) loadNibNamed:(NSString *) nibName { return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1]; } + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag { NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil]; if(!nib) return nil; UIView * target = nil; for(UIView * view in nib) { if(view.tag == tag) { target = [view retain]; break; } } if(target && [target respondsToSelector:@selector(viewDidLoad)]) { [target performSelector:@selector(viewDidLoad)]; } return [target autorelease]; } @end |
此处说明:VIEWController在iOS&mac中的视图加载较少