好的,StackOverflow上有很多关于这个的帖子,但是没有一个对解决方案特别清楚。我想创建一个带有随附的xib文件的自定义UIView
。这些要求是:
UIViewController
–一个完全独立的类我目前的做法是:
>
重写-(id)initAnd Frame:
-(id)initWithFrame:(CGRect)frame {
self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] objectAtIndex:0];
self.frame = frame;
return self;
}
在我的视图控制器中使用-(id)initWithFrame:
以编程方式实例化
MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
[self.view insertSubview:myCustomView atIndex:0];
这很好(尽管从未调用[super init]
,并且简单地使用加载的nib的内容设置对象似乎有点可疑——这里有建议在这种情况下添加子视图,这也很好)。但是,我也希望能够从故事板实例化视图。因此,我可以:
>
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeSubviews];
}
return self;
}
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initializeSubviews];
}
return self;
}
-(void)initializeSubviews {
typeof(view) view = [[[NSBundle mainBundle]
loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] objectAtIndex:0];
[self addSubview:view];
}
当然,这是行不通的,因为无论我使用上面的方法,还是我以编程方式实例化,都最终在输入-(无效)初始化子视图
并从文件加载nib时递归调用-(id)initAnd Coder:
。
其他几个SO问题涉及到这一点,比如这里,这里,这里和这里。但是,给出的答案都不能令人满意地解决问题:
有没有人能就如何解决此问题提供建议,并在定制的UIView
中以最小的麻烦/无精简控制器包装获得工作插座?或者有没有一种替代的、更干净的方法来使用最少的样板代码?
在initAnd Coder:
方法中替换自己
将失败,并出现以下错误。
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
相反,您可以使用coder:(而不是awakeFromNib
)将解码对象替换为awakafter。比如:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
当然,这也会引起递归调用问题。(故事板解码-
@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
并仅在“MyCustomView”中设置“用户定义的运行时属性”。xib'。
赞成的意见:
没有
缺点:
根本不起作用:setXib:
将在使用coder:
通常,在xib中有子视图,但在故事板中没有。
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
if(self.subviews.count > 0) {
// loading xib
return self;
}
else {
// loading storyboard
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
}
赞成的意见:
缺点:
static BOOL _loadingXib = NO;
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
if(_loadingXib) {
// xib
return self;
}
else {
// storyboard
_loadingXib = YES;
typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
_loadingXib = NO;
return view;
}
}
赞成的意见:
缺点:
例如,将\u NIB\u MyCustomView
声明为MyCustomView
的子类。并且,仅在XIB中使用\u NIB\u MyCustomView
而不是MyCustomView
。
我的客户视图。h:
@interface MyCustomView : UIView
@end
MyCustomView. m:
#import "MyCustomView.h"
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In Storyboard decoding path.
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
@interface _NIB_MyCustomView : MyCustomView
@end
@implementation _NIB_MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In XIB decoding path.
// Block recursive call.
return self;
}
@end
赞成的意见:
MyCustomView
缺点:
\u NIB\ucode>在xib界面生成器中的技巧
类似于
d)
但在故事板中使用子类,即XIB中的原始类。
在这里,我们声明
MyCustomViewProto
为MyCustomView
的子类。
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
// In storyboard decoding
// Returns MyCustomView loaded from NIB.
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self superclass])
owner:nil
options:nil] objectAtIndex:0];
}
@end
赞成的意见:
非常安全
- 清洁;在
MyCustomView
中没有额外的代码。 - 没有显式的
如果
检查相同的d)
缺点:
需要在情节提要中使用子类
我认为
e)
是最安全、最干净的策略。因此,我们在这里采用这一点。
在“awakafterusingcoder:”中加载nibNamed:
后,您必须从脚本实例中解码的self
中复制多个属性<代码>帧和自动布局/自动调整大小属性尤其重要。
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
// copy layout properities.
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
// copy autolayout constraints
NSMutableArray *constraints = [NSMutableArray array];
for(NSLayoutConstraint *constraint in self.constraints) {
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
if(firstItem == self) firstItem = view;
if(secondItem == self) secondItem = view;
[constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
// move subviews
for(UIView *subview in self.subviews) {
[view addSubview:subview];
}
[view addConstraints:constraints];
// Copy more properties you like to expose in Storyboard.
return view;
}
正如您所看到的,这是一些样板代码。我们可以将它们实现为“类别”。在这里,我扩展了常用的UIView loadFromNib
code。
#import <UIKit/UIKit.h>
@interface UIView (loadFromNib)
@end
@implementation UIView (loadFromNib)
+ (id)loadFromNib {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self)
owner:nil
options:nil] objectAtIndex:0];
}
- (void)copyPropertiesFromPrototype:(UIView *)proto {
self.frame = proto.frame;
self.autoresizingMask = proto.autoresizingMask;
self.translatesAutoresizingMaskIntoConstraints = proto.translatesAutoresizingMaskIntoConstraints;
NSMutableArray *constraints = [NSMutableArray array];
for(NSLayoutConstraint *constraint in proto.constraints) {
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
if(firstItem == proto) firstItem = self;
if(secondItem == proto) secondItem = self;
[constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
for(UIView *subview in proto.subviews) {
[self addSubview:subview];
}
[self addConstraints:constraints];
}
使用此选项,您可以像以下那样声明MyCustomViewProto
:
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
// copy additional properties as you like.
return view;
}
@end
XIB:
故事板:
结果:
我将此作为一个单独的帖子添加,以更新Swift的发布情况。LeoNatan描述的方法可以完美地在Object-C中工作。但是,更严格的编译时检查可以防止在从Swift中的xib文件加载时将自己
分配给。
因此,除了将从xib文件加载的视图添加为自定义UIView子类的子视图之外,别无选择,而不是完全替换self。这类似于原始问题中概述的第二种方法。使用该方法的Swift类的大致轮廓如下:
@IBDesignable // <- to optionally enable live rendering in IB
class ExampleView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
// below doesn't work as returned class name is normally in project module scope
/*let viewName = NSStringFromClass(self.classForCoder)*/
let viewName = "ExampleView"
let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
}
这种方法的缺点是在视图层次结构中引入了一个额外的冗余层,当使用LeoNatan在Objective-C中概述的方法时,该层不存在。但是,这可能被视为一种必要的邪恶,是Xcode中设计事物的基本方式的产物(在我看来,很难将自定义UIView类与UI布局以一种在故事板和代码上都能一致工作的方式链接起来,这似乎很疯狂)–以前从未在初始值设定项中大量替换过self
似乎是一种特别易于理解的方式,尽管每个视图基本上有两个视图类似乎也不太好。
尽管如此,这种方法的一个令人高兴的结果是,我们不再需要在interface builder中将视图的自定义类设置为我们的类文件,以确保在分配给self
时行为正确,因此,当发出loadNibNamed()
时,对init(coder-aDecoder:NSCoder)
的递归调用被中断(通过不在xib文件中设置自定义类,将调用普通UIView的init(coder-aDecoder:NSCoder)
,而不是我们的自定义版本)。
即使我们不能直接对存储在xib中的视图进行类定制,我们仍然能够使用出口/操作等将视图的文件所有者设置为我们的自定义类后,将视图链接到我们的父UIView子类:
在下面的视频中可以找到一段视频,演示如何使用这种方法一步一步地实现这种视图类。
请注意,这个质量保证(像许多一样)实际上只是具有历史意义。
(事实上,苹果终于在一段时间前添加了故事板参考,让它变得更加容易。)
这是一个典型的故事板,到处都是容器视图。一切都是容器视图。这就是你制作应用程序的方式。
(奇怪的是,KenC的回答正好说明了过去如何将xib加载到一种包装器视图中,因为您不能真正“分配给自己”。)
我读过使用XCode故事板来实例化使用XIB进行设计的视图控制器,但我在Swift(使用XCode 6 Beta 6)中遇到了困难。我想知道是我做错了什么,还是这个功能不再可用了? 我创建了一个简单的存储库https://github.com/jer-k/storyboardtesting-swift,它展示了上述方法。 我通过添加并重写到init来解决这个问题 但我想知道是否仍有可能让故事板为我
问题内容: 在XCode 6中创建一个新项目不允许禁用Storyboard。您只能选择Swift或Objective-C,而不能使用Core Data。 我尝试删除情节提要,并从项目中删除主情节提要,然后从didFinishLaunching手动设置窗口 在AppDelegate中,我有以下内容: 但是,XCode给我一个错误: 类“ AppDelegate”没有初始化程序 有人成功吗? 问题答案
问题内容: 我正在这样做,我想使用CollectionView,但我还没有看到原型单元,也不知道在这种情况下如何使用CollectionView,有人可以帮助我吗? 我尝试以这种方式使用,但是比UICollectionView要花很多时间并且很难管理 问题答案: 使用UICollectionView的主要方法是通过编程方式管理逻辑。 首先,创建一个继承自的新类。选择是否要包含xib来轻松设计单元格
问题内容: 我有一个没有情节提要的应用程序,所有UI创建都是通过代码完成的,我得到了一个我可以使其在iPhone上使用的应用程序,因为该应用程序最初仅是为iPad设计的,因此当您在列表中选择一行时,大师认为,它在iPhone上什么也不做,但在iPad上工作正常。 所以我的问题是我可以创建并执行允许在方法上显示“详细视图”的segue 吗? 到目前为止,这是我所做的: 但是当运行并选择一行时,应用程
问题内容: 我要执行以下操作: 我已经尝试过了,但是它似乎并不总是可以工作。它在一个上下文中可以工作,但是在另一个上下文中,相同的代码在第二个“ Class.forName(” MyClass“)”上崩溃了…… 总是调用带来正确的课堂,并尝试过,但没有区别。不知何故,在某些情况下,第二个Class.forName找到了该类,而在其他情况下,它只是坏了……我错过了什么吗? 问题答案: 我发现我的代码
问题内容: 这是我的UIView类: 它应该只填满屏幕。当我在情节提要中添加具有固定到屏幕边缘的约束的视图时,启动应用程序时它将崩溃。如上面的代码所述,出现错误。 通过此init函数以编程方式创建和添加子类: 但是我希望它可以通过Storyboard重复使用,而无需添加代码。我的代码有什么问题,甚至有可能我想要什么? 谢谢! 问题答案: 启动应用程序时崩溃 因为那是您的代码 告诉 它要做的。 意思