如何使用用Interface Builder创build的nib文件加载UIView
我试图做一些细致的事情,但应该是可能的。 所以这里是你所有的专家的挑战(这个论坛是你们很多的:))。
我正在创build一个调查问卷的“组件”,我想加载一个NavigationContoller
(我的QuestionManagerViewController
)。 “组件”是一个“空的” UIViewController
,它可以根据需要回答的问题加载不同的视图。
我这样做的方式是:
- 创buildQuestion1View对象作为
UIView
子类,定义一些IBOutlets
。 - 创build(使用界面生成器)
Question1View.xib
(这里是我的问题可能是 )。 我将UIViewController
和UIView
都设置为Question1View类。 - 我使用视图的组件(使用IB)链接sockets。
-
我重写我的
QuestionManagerViewController
的initWithNib
看起来像这样:- (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]由于未捕获的exception'
NSInternalInconsistencyException
',原因:'-[UIViewController _loadViewFromNibNamed:bundle:]
加载了“Question1View”没有设置。
我敢肯定,有一种方法可以使用nib文件加载视图,而不需要创build一个viewController类。
还有一种更简单的方法来访问视图,而不是将数组作为一个数组来处理。
1 )创build一个自定义的View子类,其中包含您希望稍后访问的任何sockets。 – 我的看法
2 )在UIViewController中,你想要加载和处理的笔尖,创build一个IBOutlet属性,将举行加载的笔尖的视图,例如
在MyViewController(一个UIViewController子类)
@property (nonatomic, retain) IBOutlet UIView *myViewFromNib;
(不要忘记综合它,并释放你的.m文件)
3)在IB中打开你的笔尖(我们称之为'myViewNib.xib'),将你的文件的所有者设置为MyViewController
4)现在将你的文件的Owner出口myViewFromNib连接到笔尖的主视图。
5)现在在MyViewController中,写下面一行:
[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];
现在只要你这样做,调用你的财产“self.myViewFromNib”将让你访问从你的笔尖视图!
谢谢你们。 我确实find了一个做我想做的事的方法。
- 用你需要的
IBOutlet
创build你的UIView
。 - 在IB中创buildxib,按照喜欢的方式devise它,并像下面这样链接它:文件的所有者是类
UIViewController
(没有自定义的子类,而是“真正的”)。 文件所有者的视图连接到主视图,其类被声明为步骤1)中的一个。 - 将您的控件与
IBOutlet
连接起来。 -
DynamicViewController
可以运行它的逻辑来决定加载什么view / xib。 一旦它做出决定,在loadView
方法中就像这样:NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil]; QPickOneView* myView = [ nibViews objectAtIndex: 1]; myView.question = question;
而已!
主包的loadNibNamed
方法将负责初始化视图并创build连接。
现在,ViewController可以根据内存中的数据显示一个视图或另一个视图,“父”屏幕不需要打扰这个逻辑。
我不确定一些答案是在谈论什么,但是我需要在这里回答这个问题,以便我下次在Google中search。 关键字:“如何从nib加载UIView”或“如何从NSBundle加载UIView”
从Apress Beginning iPhone 3手册(第247页,“使用新的表格视图单元格”),几乎100%的代码如下:
- (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]; }
这个假设你有一个名为Blah
的UIView
子类,一个名为Blah
的nib包含一个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
Swift Extension
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 } }
并使用一个例子:
class SomeView: UIView { class func loadFromNib() -> SomeView? { return self.loadFromNib(withName: "SomeView") } }
对于所有那些需要pipe理自定义视图的多个实例,那就是一个Outlet集合 ,我以这种方式合并和自定义@Gonso,@AVeryDev和@Olie的答案:
-
创build一个自定义
MyView : UIView
并将其设置为所需XIB中的根UIView
“ 自定义类 ”; -
在
MyView
创build你需要的所有sockets (现在这样做,因为在点3之后,IB会build议你将sockets连接到UIViewController
而不是我们想要的自定义视图); -
将你的
UIViewController
设置为自定义视图XIB的 “ 文件所有者 ” -
在
UIViewController
为每个你想要的MyView
实例添加一个新的UIViews
,并将它们连接到UIViewController
创build一个Outlet集合 :这些视图将作为自定义视图实例的“包装”视图; -
最后,在你的
UIViewController
的viewDidLoad
中添加下面几行:
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被重用
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请注意, [UINib instantiateWithOwner]
返回一个数组,所以你应该使用反映你想要重用的UIView的元素。 在这种情况下,这是第一个元素。
您不应该将视图控制器的类设置为Interface Builder中的UIView
的子类。 这绝对是你问题的一部分。 把它当作UIViewController
,它的一些子类,或者你有的其他自定义类。
至于从xib只加载一个视图,我是在假设你必须有某种视图控制器(即使它不扩展UIViewController
,这可能是太重量级为您的需要)设置为文件的所有者在Interface Builder如果你想用它来定义你的接口。 我做了一些研究来证实这一点。 这是因为否则就无法访问UIView
中的任何接口元素,也没有办法让代码中的自己的方法被事件触发。
如果你使用一个UIViewController
作为你的视图的文件所有者,你可以使用initWithNibName:bundle:
来加载它并获取视图控制器对象。 在IB中,确保使用xib中的界面将view
出口设置为view
。 如果您使用其他types的对象作为文件的所有者,则需要使用NSBundle
的loadNibNamed:owner:options:
方法来加载nib,将文件所有者的实例传递给方法。 所有的属性将根据您在IB中定义的出口来设置。
你也可以使用UIViewController的initWithNibName而不是loadNibNamed。 我觉得这很简单。
UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil]; [self.subview addSubview:aViewController.view]; [aViewController release]; // release the VC
现在你只需要创buildMySubView.xib和MySubView.h / m。 在MySubView.xib中,将文件的所有者类设置为UIViewController,并将类视图设置为MySubView。
您可以使用父级xib文件来定位subview
大小。
这是一个很好的问题(+1),答案几乎很有帮助;)抱歉的家伙,但是我经历了一段时间,尽pipeGonso和AVeryDev都给出了很好的提示。 希望这个答案能帮助别人。
MyVC
是拥有所有这些东西的视图控制器。
MySubview
是我们想从xib加载的视图
- 在MyVC.xib中,创build一个types为
MySubView
的视图,该视图的大小和形状正确,并放置在您想要的位置。 -
在MyVC.h中,有
IBOutlet MySubview *mySubView // ... @property (nonatomic, retain) MySubview *mySubview;
-
在MyVC.m中,
@synthesize mySubView;
不要忘记在dealloc
释放它。 - 在MySubview.h中,有一个
UIView *view
的出口/属性(可能是不必要的,但为我工作。)合成和释放.m - 在MySubview.xib中
- 将文件所有者types设置为
MySubview
,并将视图属性链接到您的视图。 - 布置所有的位,并根据需要连接到
IBOutlet
- 将文件所有者types设置为
-
回到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];
对我来说,棘手的一点是:其他答案中的提示从xib加载了我的视图,但是并没有replaceMyVC中的视图(杜!) – 我必须自己交换视图。
此外,要访问mySubview
的方法,必须将MySubview
文件中的view
属性设置为MySubview
。 否则,它会作为一个普通的UIView
回来。
如果有一种方法可以直接从自己的xib载入mySubview,那会很好,但是这让我在需要的地方。
这是应该更容易的事情。 我结束了扩展UIViewController
并添加一个loadNib:inPlaceholder:
select器。 现在我可以说
self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];
下面是这个类别的代码(和Gonso描述的一样:
@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
我也想做类似的事情,这就是我发现的:(SDK 3.1.3)
我有一个视图控制器A(本身拥有一个导航控制器)加载VC Bbutton按下:
在AViewController.m中
BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil]; [self.navigationController pushViewController:bController animated:YES]; [bController release];
现在VC B有它的Bnib接口,但是当按下一个button时,我想进入一个“编辑模式”,这个模式有一个独立的用户界面,但是我不想要一个新的编辑模式的VC,我希望新的笔尖与我现有的B VC相关联。
所以,在BViewController.m(button按下的方法)
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil]; UIView *theEditView = [nibObjects objectAtIndex:0]; self.editView = theEditView; [self.view addSubview:theEditView];
然后在另一个button上按(退出编辑模式):
[editView removeFromSuperview];
我回到了我原来的Bnib。
这工作正常,但请注意我的EditMode.nib只有1个顶级obj,它是一个UIView obj。 无论这个笔尖中的文件所有者是否设置为BViewController或默认的NSObject,但是请确保文件所有者中的View Outlet没有设置为任何内容。 如果是这样,那么我得到一个exc_bad_access崩溃,xcode继续加载6677堆栈帧,显示内部UIView方法,反复调用…所以看起来像一个无限循环。 (然而,View Outlet是在我原来的Bnib中设置的)
希望这可以帮助。
我做了一个我喜欢的类别:
UIView+NibInitializer.h
#import <UIKit/UIKit.h> @interface UIView (NibInitializer) - (instancetype)initWithNibNamed:(NSString *)nibNameOrNil; @end
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
然后,像这样打电话:
MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];
如果您的笔尖被命名为您的class级名称以外的名称,请使用笔尖名称。
要在其子类中覆盖它以获取其他行为,可能如下所示:
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil { self = [super initWithNibNamed:nibNameOrNil]; if (self) { self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0; } return self; }
迅速
其实我对这个问题的解决方法是,加载在我的CustonViewController viewDidLoad视图中,我想要使用这样的视图:
myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView
不要在loadView()
方法中加载视图! loadView方法用于为您的自定义ViewController加载视图。
我发现这篇由Aaron Hillegass(作者,导师,cocoa忍者) 发表的博文非常有启发性。 即使你不采用他的修改方法通过指定的初始化程序来加载NIB文件,你至less可以更好地理解正在进行的过程。 最近我一直在使用这种方法取得巨大的成功!
对于具有可devise选项的Swift用户:
- 创build一个自定义的
UIView
子类和一个xib文件 ,我们将以自己的类名命名:在我们的MemeView中。 在Meme View类中,请记住在类声明之前使用@IBDesignable
属性将其定义为可devise的 -
Rember使用我们在Indetity Inspector面板中的自定义
UIView
子类在xib中设置文件的所有者 -
现在我们可以在xib文件中构build我们的界面,制作约束条件,创build商店,操作等。
-
我们需要实现一些方法到我们的自定义类来打开一旦初始化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 }
}
-
在我们的自定义类中,我们也可以定义一些无法指定的属性,从界面生成器完全控制它们
@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!
}
几个例子:
如果我们需要在故事板或另一个xib中显示视图时添加更多的信息,为了实现这一目的,我们可以实现prepareForInterfaceBuilder()
,只有在界面生成器中打开文件时才会执行此方法。 如果你做了我写的所有东西,但没有任何工作,这是一种通过在其实现中添加断点来debugging单个视图的方法。
这是视图层次结构。
希望这有助于完整的示例可以在这里下载
以前的答案没有考虑到在iPhone SDK 2.0和2.1之间发生的NIB(XIB)结构的变化。 用户内容现在从索引0而不是1开始。
你可以使用2.1版本以上的2.1版本的macros(这是IPHONE之前的两个下划线:
// 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文件加载视图),但我需要完全从UIView
的子类的子类的上下文(即不以任何方式涉及视图控制器)完成此操作。 为此,我创build了这个实用方法:
+ (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; }
然后我从我的子类的initWithFrame
方法调用它如下所示:
- (id)initWithFrame:(CGRect)frame { self = [Utilities initWithNibName:@"XIB1" withSelf:self]; if (self) { // Initialization code. } return self; }
发布一般的兴趣; 如果有人不这样做,请告诉我。
这是一个在Swift中完成的方法(目前在XCode 7 beta 5中编写Swift 2.0)。
从您在Interface Builder中设置为“Custom Class”的UIView
子类创build一个像这样的方法(我的子类叫做RecordingFooterView):
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 }
那么你可以这样称呼它:
let recordingFooterView = RecordingFooterView.loadFromNib()
没有答案解释如何创build这个问题的根源的独立XIB。 没有Xcode 4
选项来“创build新的XIB文件”。
去做这个
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)将加载的视图附加到视图控制器的视图:
[self.view addSubview:myViewFromNib];
据推测,有必要从视图中删除它,以避免内存泄漏。
为了澄清:视图控制器有几个IBOutlets,其中一些连接到原始的笔尖文件中的项目(像往常一样),一些连接到加载的笔尖的项目。 两个笔尖都拥有相同的所有者类别。 加载的视图覆盖原来的一个。
提示:将加载的笔尖的主视图的不透明度设置为零,然后不会遮挡原始笔尖的项目。
花了好几个小时后,我find了如下的解决办法。 按照以下步骤创build自定义的UIView
。
1)创build从UIViewinheritance的类myCustomView 。
2)用名称myCustomView创build.xib 。
3)在你的.xib文件中改变UIView类,在那里指定myCustomView类。
4)创buildIBOutlets
5)在myCustomView * customView中加载.xib 。 使用下面的示例代码。
myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0]; [myView.description setText:@"description"];
注意:对于那些仍然面临问题的人可以发表评论,我会给他们提供示例项目与myCustomView的链接
我有一个命名xibs的视图在他们的意见相同的惯例。 和一个视图控制器一样。 然后,我不必在代码中写出类名。 我从同名的nib文件加载UIView。
一个名为MyView的类的示例。
- 在Interface Builder中创build一个名为MyView.xib的nib文件
- 在界面生成器中,添加一个UIView。 将其类设置为MyView。 自定义您的内容,将MyView的实例variables连接到您可能希望稍后访问的子视图。
-
在你的代码中,像这样创build一个新的MyView:
MyView * myView = [MyView nib_viewFromNibWithOwner:owner];
这是这个类别:
@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 { }
然后,我将使用来自我正在使用的控制器的动作连接button,并使用自定义视图子类中的插口将标签上的内容设置为标签。
我结束了为这个UIView添加一个类别:
#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是更less的视图加载在iOS和MAC