Ken*_*eld 79 objective-c storyboard uiview nib ios
好的,StackOverflow上有很多关于此的帖子,但没有一个在解决方案上特别清楚.我想UIView
用随附的xib文件创建一个自定义.要求是:
UIViewController
- 一个完全独立的类我目前的做法是:
覆盖 -(id)initWithFrame:
-(id)initWithFrame:(CGRect)frame {
self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:self
options:nil] objectAtIndex:0];
self.frame = frame;
return self;
}
Run Code Online (Sandbox Code Playgroud)-(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];
Run Code Online (Sandbox Code Playgroud)这工作正常(虽然从来没有调用[super init]
,只是使用加载的笔尖的内容设置对象似乎有点怀疑 - 这里有建议添加一个子视图在这种情况下也工作正常).但是,我希望能够从故事板中实例化视图.所以我可以:
UIView
在故事板中放置父视图MyCustomView
覆盖-(id)initWithCoder:
- 我见过的代码最常适合以下模式:
-(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];
}
Run Code Online (Sandbox Code Playgroud)当然,这不起作用,因为我是否使用上面的方法,或者我是否以编程方式实例化,最终都以递归方式调用从文件-(id)initWithCoder:
输入-(void)initializeSubviews
和加载nib.
其他几个SO问题就像这里,这里,这里和这里一样.但是,没有给出令人满意的答案解决问题:
任何人都可以提供有关如何解决此问题的建议,并在自定义中获得工作插座UIView
,最小化/没有薄控制器包装?或者是否有一种替代的,更清洁的方式来处理最少的样板代码?
Fat*_*tie 26
请注意,此QA(与许多人一样)实际上只是具有历史意义.
如今多年来在iOS中,一切都只是一个容器视图.完整的教程在这里
(事实上,Apple 现在不久前添加了Storyboard References,使其变得更加容易.)
这是一个典型的故事板,到处都有容器视图.一切都是容器视图.这就是你制作应用程序的方式.
(作为一种好奇心,KenC的答案确切地说明了如何将xib加载到一种包装器视图中,因为你无法真正"分配给自己".)
Ken*_*eld 23
我将此作为一个单独的帖子添加,以更新Swift的发布情况.LeoNatan描述的方法在Objective-C中完美运行.但是,更严格的编译时检查阻止self
在从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
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法的缺点是在视图层次结构中引入了一个额外的冗余层,当使用LeoNatan在Objective-C中概述的方法时,该层不存在.然而,这可以被视为一种必要的邪恶,并且是Xcode中设计事物的基本方式的产物(对我来说,似乎很难将自定义UIView类与UI布局以一致的方式链接起来是如此困难在故事板和代码中) - self
在初始化程序中替换批处理之前似乎永远不是一种特别可解释的处理方式,尽管每个视图基本上有两个视图类似乎也不是那么好.
尽管如此,这种方法的一个令人满意的结果是我们不再需要将视图的自定义类设置为接口构建器中的类文件,以确保在分配时的正确行为self
,因此init(coder aDecoder: NSCoder)
在发出时的递归调用loadNibNamed()
被破坏(通过不设置在xib文件中的自定义类,init(coder aDecoder: NSCoder)
而不是我们的自定义版本将调用普通的UIView而不是自定义版本.
即使我们不能直接对存储在xib中的视图进行类自定义,我们仍然可以在将视图的文件所有者设置为自定义类之后,使用outlet/actions等将视图链接到我们的"父"UIView子类:
可以在以下视频中找到使用此方法逐步演示此类视图类的实现的视频.
rin*_*aro 16
self
从故事板替换更换self
的initWithCoder:
方法将失败,下面的错误.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Run Code Online (Sandbox Code Playgroud)
相反,您可以用awakeAfterUsingCoder:
(不awakeFromNib
)替换已解码的对象.喜欢:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
Run Code Online (Sandbox Code Playgroud)
当然,这也会导致递归调用问题.(故事板解码 - > awakeAfterUsingCoder:
- > loadNibNamed:
- > awakeAfterUsingCoder:
- > loadNibNamed:
- > ...)
所以你必须检查awakeAfterUsingCoder:
故事板解码过程或XIB解码过程中调用的当前事件.你有几种方法可以做到这一点:
@property
仅在NIB中设置的private .@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
Run Code Online (Sandbox Code Playgroud)
并在"MyCustomView.xib"中设置"用户定义的运行时属性".
优点:
缺点:
setXib:
将被称为AFTER awakeAfterUsingCoder:
self
有任何子视图通常,您在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];
}
}
Run Code Online (Sandbox Code Playgroud)
优点:
缺点:
loadNibNamed:
通话期间设置静态标志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;
}
}
Run Code Online (Sandbox Code Playgroud)
优点:
缺点:
例如,声明_NIB_MyCustomView
为.的子类MyCustomView
.并且,仅使用_NIB_MyCustomView
而不是MyCustomView
在您的XIB中.
MyCustomView.h:
@interface MyCustomView : UIView
@end
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
优点:
if
的MyCustomView
缺点:
_NIB_
xib Interface Builder中的前缀技巧类似d)
但在Storyboard中使用子类,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
Run Code Online (Sandbox Code Playgroud)
优点:
MyCustomView
.if
检查相同d)
缺点:
我认为这e)
是最安全,最干净的策略.所以我们在这里采用它.
之后loadNibNamed:
在"awakeAfterUsingCoder:",你必须复制从几个属性self
被解码例如F中的故事板.frame
和autolayout/autoresize属性尤为重要.
- (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;
}
Run Code Online (Sandbox Code Playgroud)
如您所见,这是一些样板代码.我们可以将它们实现为"类别".在这里,我扩展了常用的UIView+loadFromNib
代码.
#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];
}
Run Code Online (Sandbox Code Playgroud)
使用此,您可以声明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
Run Code Online (Sandbox Code Playgroud)
厦门国际银行:
故事板:
结果:
Leo*_*ica 13
你的问题是loadNibNamed:
来自(后代)initWithCoder:
.loadNibNamed:
内部呼叫initWithCoder:
.如果你想覆盖storyboard编码器,并且总是加载你的xib实现,我建议使用以下技术.向视图类添加属性,并在xib文件中将其设置为预定值(在"用户定义的运行时属性"中).现在,在调用之后[super initWithCoder:aDecoder];
检查属性的值.如果是预定值,请不要打电话[self initializeSubviews];
.
所以,像这样:
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self && self._xibProperty != 666)
{
//We are in the storyboard code path. Initialize from the xib.
self = [self initializeSubviews];
//Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
//self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:@"backgroundColor"];
}
return self;
}
-(instancetype)initializeSubviews {
id view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
return view;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
60668 次 |
最近记录: |