我正在尝试做一些精心设计的事情,但这应该是可行的。所以这里对你所有的专家都是一个挑战(这个论坛是你们很多人的一部分:))。
我正在创建一个调查问卷“组件”,我想加载NavigationContoller
(我的QuestionManagerViewController
)。 “组件”是一个“空”UIViewController
,可根据需要回答的问题加载不同的视图。
我这样做的方式是:
UIView
子类,定义一些IBOutlets
。Question1View.xib
(这里我的问题可能在哪里)。我将UIViewController
和UIView
都设置为Question1View类。 我覆盖initWithNib
的{{1}},如下所示:
QuestionManagerViewController
当我运行代码时,我收到此错误:
2009-05-14 15:05:37.152 iMobiDines [17148:20b] ***因未捕获的异常“
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) { // Custom initialization } return self; }
”终止应用,原因:'NSInternalInconsistencyException
已加载“Question1View”笔尖但是没有设置视图插座。'
我确信有一种方法可以使用nib文件加载视图,而无需创建viewController类。
答案 0 :(得分:223)
还有一种更简单的方法来访问视图,而不是将nib作为数组处理。
1 )创建一个自定义View子类,其中包含您希望以后有权访问的任何出口。 --MyView
要在UIViewController中加载和处理nib的2 ),创建一个IBOutlet属性来保存加载的nib的视图,例如
在MyViewController(一个UIViewController子类)
@property (nonatomic, retain) IBOutlet UIView *myViewFromNib;
(别忘了合成它并在你的.m文件中发布)
3)在IB中打开你的笔尖(我们称之为'myViewNib.xib'),将文件的所有者设置为 MyViewController
4)现在将文件的所有者插座myViewFromNib连接到笔尖中的主视图。
5)现在在MyViewController中,写下以下一行:
[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];
现在,只要您这样做,调用您的属性“self.myViewFromNib”将允许您从笔尖访问该视图!
答案 1 :(得分:119)
谢谢大家。 我确实找到了一种方法来做我想做的事。
UIView
创建IBOutlet
。UIViewController
(没有自定义子类,但是“真正的”子类)。文件所有者的视图连接到主视图,其类声明为步骤1)中的类。IBOutlet
s。 DynamicViewController
可以运行其逻辑来决定要加载的视图/ xib。一旦它做出了决定,用loadView
方法做了这样的事情:
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
owner:self
options:nil];
QPickOneView* myView = [ nibViews objectAtIndex: 1];
myView.question = question;
就是这样!
主要包的loadNibNamed
方法将负责初始化视图和创建连接。
现在ViewController可以显示一个或另一个视图,具体取决于内存中的数据,“父”屏幕不需要打扰这个逻辑。
答案 2 :(得分:68)
我不确定某些答案在谈论什么,但我需要在下次搜索Google时将此答案放在此处。关键字:“如何从笔尖加载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];
}
这假设您有一个名为UIView
的{{1}}子类,一个名为Blah
的笔尖,其中包含Blah
,其类别设置为UIView
。< / p>
Blah
#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
使用中的一个例子:
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
}
}
答案 3 :(得分:22)
对于那些需要管理自定义视图的多个实例,即 Outlet Collection 的人,我以这种方式合并并自定义@Gonso,@ AveryDev和@Olie答案:
创建自定义MyView : UIView
并将其设置为所需 XIB 中根UIView
的“自定义类”;
在MyView
中创建您需要的所有商店(现在就做,因为在第3点之后,IB会建议您将商店连接到UIViewController
而不是我们想要的自定义视图);
将自定义视图 XIB 的UIViewController
设为“文件所有者”;
在UIViewController
为您想要的每个UIViews
实例添加新的MyView
,并将它们连接到UIViewController
,创建 Outlet Collection < / strong>:这些视图将充当自定义视图实例的“包装”视图;
最后,在viewDidLoad
的{{1}}添加以下内容:
UIViewController
答案 4 :(得分:18)
我会使用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的元素。在这种情况下,它是第一个元素。
答案 5 :(得分:10)
您也可以使用UIViewController的initWithNibName而不是loadNibNamed。我发现它更简单。
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中,将File的Owner类设置为UIViewController,并将类查看为MySubView。
您可以使用父xib文件定位subview
。
答案 6 :(得分:10)
您不应该将视图控制器的类设置为Interface Builder中UIView
的子类。这绝对是你问题的至少一部分。将其保留为UIViewController
,它的某个子类或您拥有的其他自定义类。
至于只从xib加载一个视图,我假设你有拥有某种视图控制器(即使它没有扩展UIViewController
,如果你想用它来定义你的界面,那么在Interface Builder中设置为文件所有者可能对你的需求来说太重了。我做了一些研究来证实这一点。这是因为否则将无法访问UIView
中的任何界面元素,也无法通过事件触发代码中自己的方法。
如果您使用UIViewController
作为视图的文件所有者,则可以使用initWithNibName:bundle:
加载它并获取视图控制器对象。在IB中,请确保将view
插座设置为xib中具有接口的视图。如果您使用其他类型的对象作为文件所有者,则需要使用NSBundle
的{{1}}方法加载nib,将File的所有者实例传递给该方法。其所有属性将根据您在IB中定义的出口进行适当设置。
答案 7 :(得分:8)
这是一个很好的问题(+1),答案几乎很有帮助;)对不起,伙计们,但我有一段时间不知所措,尽管Gonso&amp; AVeryDev提供了很好的提示。希望这个答案能够帮助其他人。
MyVC
是控制所有这些东西的视图控制器。
MySubview
是我们想要从xib加载的视图
MySubView
的视图,该视图的大小和尺寸正确。形状与定位在你想要的地方。在MyVC.h中,有
IBOutlet MySubview *mySubView
// ...
@property (nonatomic, retain) MySubview *mySubview;
在MyVC.m中,@synthesize mySubView;
并且不要忘记在dealloc
中发布它。
UIView *view
的插座/属性(可能没必要,但对我有效。)合成&amp;在.m 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];
对我来说棘手的一点是:其他答案中的提示从xib加载了我的视图,但没有替换MyVC中的视图(呃!) - 我不得不自己交换它。
此外,要访问mySubview
的方法,.xib文件中的view
属性必须设置为MySubview
。否则,它会以简单的UIView
返回。
如果有一种方法可以直接从自己的xib加载mySubview
,那就会摇滚,但这让我得到了我需要的位置。
答案 8 :(得分:8)
这应该更容易。我最终扩展了UIViewController
并添加了loadNib:inPlaceholder:
选择器。现在我可以说
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
答案 9 :(得分:6)
我也想做类似的事情,这就是我发现的: (SDK 3.1.3)
我有一个视图控制器A(由Nav控制器拥有),它按下按钮上的VC B:
在AViewController.m中
BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];
现在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];
然后按另一个按钮(退出编辑模式):
[editView removeFromSuperview];
我回到原来的Bnib。
这很好用,但请注意我的EditMode.nib中只有一个顶级obj,一个UIView obj。 此笔尖中的文件所有者是否设置为BViewController或默认NSObject并不重要,但请确保文件所有者中的视图插座未设置为任何内容。 如果是,那么我得到一个exc_bad_access崩溃,xcode继续加载6677堆栈帧 显示一个内部UIView方法反复调用...所以看起来像一个无限循环。 (但是,我的原始Bnib中设置了View Outlet)
希望这有帮助。
答案 10 :(得分:6)
快速
实际上我对这个问题的解决方法是,在我的CustonViewController中的viewDidLoad中加载视图,我想在那里使用这样的视图:
myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView
请勿以loadView()
方式加载视图! loadView方法用于加载自定义ViewController的视图。
答案 11 :(得分:6)
我做了一个我喜欢的类别:
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];
如果您的笔尖的名称不是您班级的名称,请使用笔尖名称。
要在子类中覆盖它以获得其他行为,它可能如下所示:
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
self = [super initWithNibNamed:nibNameOrNil];
if (self) {
self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
}
return self;
}
答案 12 :(得分:5)
对于具有可设计选项的Swift用户:
UIView
子类和一个xib文件,我们将在我们自己的类名后面命名:在我们的例子中是MemeView。在Meme View类中,记得在类声明@IBDesignable
属性将其定义为可设计
使用我们在Indetity Inspector面板中的自定义UIView
子类在xib中设置文件所有者
我们需要为自定义类实现一些方法,以便在初始化后打开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()
,只有在界面构建器中打开文件时才会执行此方法。
如果您执行了我编写的所有操作但没有任何工作,则可以通过在其实现中添加断点来调试sigle视图。
这是视图层次结构。
希望这有助于下载完整的样本here
答案 13 :(得分:4)
我发现Aaron Hillegass(作者,导师,Cocoa ninja)的this blog posting非常有启发性。即使您没有采用他修改过的方法通过指定的初始化程序加载NIB文件,您也可能至少可以更好地了解正在进行的过程。我最近一直在使用这种方法取得巨大成功!
答案 14 :(得分:3)
我有理由做同样的事情(以编程方式从XIB文件加载视图),但我需要完全从UIView
的子类的子类的上下文中完成(即不涉及以任何方式查看控制器)。为此,我创建了这个实用程序方法:
+ (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;
}
张贴一般兴趣;如果有人在没有这样做的情况下看到任何问题,请告诉我。
答案 15 :(得分:3)
这是Swift中的一种方法(目前在XCode 7 beta 5中编写Swift 2.0)。
从您设置为&#34;自定义类&#34;的UIView
子类中在Interface Builder中创建一个这样的方法(我的子类叫做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()
答案 16 :(得分:3)
上一个答案没有考虑到iPhone SDK 2.0和2.1之间发生的NIB(XIB)结构的变化。用户内容现在从索引0而不是1开始。
您可以使用对所有版本2.1及更高版本有效的2.1宏(在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;
我们在大多数应用程序中使用与此类似的技术。
巴尼
答案 17 :(得分:2)
没有一个答案解释了如何创建作为此问题根源的独立XIB。 “创建新的XIB文件”没有Xcode 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”没有出现在任何地方,它可以节省你几分钟的时间。
答案 18 :(得分:1)
@AVeryDev
6)要将加载的视图附加到视图控制器的视图中:
[self.view addSubview:myViewFromNib];
据推测,有必要将其从视图中删除以避免内存泄漏。
澄清一下:视图控制器有几个IBOutlet,其中一些连接到原始nib文件中的项目(像往常一样),有些连接到加载的nib中的项目。两个笔尖都有相同的所有者类。加载的视图覆盖原始视图。
提示:将加载的笔尖中主视图的不透明度设置为零,然后它不会遮挡原始笔尖中的项目。
答案 19 :(得分:1)
花了很多时间后,我伪造了以下解决方案。
请按照以下步骤创建自定义UIView
。
1)创建继承自 UIView 的课程 myCustomView 。
3)更改.xib文件中的 UIView 类,在那里指定 myCustomView 类。
5)在 myCustomView * customView 中加载.xib。 使用以下示例代码。
myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];
注意:对于那些仍然面临问题的人可以发表评论,我会为他们提供链接 使用 myCustomView
的示例项目
答案 20 :(得分:1)
最短版本:
RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
答案 21 :(得分:0)
我有一个约定xibs的约定,其中的视图与视图相同。与视图控制器相同。然后,我不必在代码中写出类名。我从具有相同名称的nib文件加载UIView。
名为MyView的类的示例。
在您的代码中,创建一个新的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 {
}
然后我会使用我正在使用的控制器中的操作连接按钮,并使用我的自定义视图子类中的插座在标签上设置内容。
答案 22 :(得分:0)
以编程方式从Swift 4中的nib / xib加载视图:
// 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
}
答案 23 :(得分:-1)
我最终为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