[英]How to embed a custom view xib in a storyboard scene?
我在XCode / iOS世界中相对较新; 我已经完成了一些不错的基于故事板的应用程序,但我从来没有在整个nib / xib上扯掉我的东西。 我想使用相同的工具为场景设计/布局可重用的视图/控件。 所以我为我的视图子类创建了我的第一个xib并绘制了它:
我已连接我的插座和约束设置,就像我以前在故事板中做的那样。 我将我的File Owner
的类设置为我的自定义UIView
子类的类。 所以我假设我可以用一些API实例化这个视图子类,它将如图所示配置/连接。
现在回到我的故事板中,我想嵌入/重用它。 我在表视图原型单元格中这样做:
我有一个观点。 我已将它的类设置为我的子类。 我为它创建了一个插座,所以我可以操纵它。
64美元的问题是在哪里/如何表明仅仅放置我的视图子类的空/未配置实例是不够的,但是使用我创建的.xib来配置/实例化它? 这真的很酷,如果在XCode6中,我可以输入XIB文件用于给定的UIView,但我没有看到这样做的字段,所以我假设我必须在代码中的某些地方做一些事情。
(我确实在SO上看到了这样的其他问题,但是没有找到任何关于这部分难题的问题,或者最新的XCode6 / 2015)
我可以通过实现我的表格单元格的awakeFromNib
来实现这一点,如下所示:
- (void)awakeFromNib
{
// gather all of the constraints pointing to the uncofigured instance
NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
}]];
// fetch the fleshed out variant
ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
// ape the current placeholder's frame
fromXIB.frame = self.progressControl.frame;
// now swap them
[UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
// recreate all of the constraints, but for the new guy
for (NSLayoutConstraint *each in progressConstraints) {
id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
[self.contentView addConstraint: constraint];
}
// update our outlet
self.progressControl = fromXIB;
}
这很容易吗? 还是我为此努力工作?
你快到了。 您需要在分配视图的自定义类中覆盖initWithCoder。
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
}
return self; }
一旦完成,StoryBoard将知道在UIView中加载xib。
这是一个更详细的解释:
这就是你的故事板上你的UIViewController
样子:
蓝色空间基本上是一个将“保持”你的xib的UIView。
这是你的xib:
有一个Action连接到它上面的一个按钮,它将打印一些文本。
这是最终的结果:
第一个clickMe和第二个之间的区别在于第一个是使用StoryBoard
添加到UIViewController
的。 第二个是使用代码添加的。
您需要在自定义UIView
子类中实现awakeAfterUsingCoder:
. 此方法允许您将解码的对象(来自故事板)与不同的对象(来自可重用的xib)交换,如下所示:
- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
// without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
// set the view tag in the MyView xib to be -999 and anything else in the storyboard.
if ( self.tag == -999 )
{
return self;
}
// make sure your custom view is the first object in the nib
MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];
// copy properties forward from the storyboard-decoded object (self)
v.frame = self.frame;
v.autoresizingMask = self.autoresizingMask;
v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
v.tag = self.tag;
// copy any other attribtues you want to set in the storyboard
// possibly copy any child constraints for width/height
return v;
}
这里有一篇非常好的文章,讨论了这种技术和一些替代方案。
此外,如果将IB_DESIGNABLE
添加到@interface声明中,并提供initWithFrame:
方法,则可以在IB中使用设计时预览(需要Xcode 6!):
IB_DESIGNABLE @interface MyView : UIView
@end
@implementation MyView
- (id) initWithFrame: (CGRect) frame
{
self = [[[UINib nibWithNibName: @"MyView"
bundle: [NSBundle bundleForClass: [MyView class]]]
instantiateWithOwner: nil
options: nil] firstObject];
self.frame = frame;
return self;
}
这个Interface Builder和Swift 4的一种非常酷且可重用的方式:
像这样创建一个新类:
import Foundation import UIKit @IBDesignable class XibView: UIView { @IBInspectable var xibName: String? override func awakeFromNib() { guard let name = self.xibName, let xib = Bundle.main.loadNibNamed(name, owner: self), let view = xib.first as? UIView else { return } self.addSubview(view) } }
在您的故事板中,添加一个UIView,它将充当Xib的容器。 给它一个XibView
的类名:
在此新XibView
的属性检查器中,在IBInspectable字段中设置XibView
的名称(不带文件扩展名):
将新的Xib视图添加到项目中,并在属性检查器中,将Xib的“文件所有者”设置为XibView
(确保您只将“文件所有者”设置为自定义类,不要将内容视图子类化,或者它将崩溃),并再次设置IBInspectable字段:
有一点需要注意:这假设您将.xib框架与其容器匹配。 如果你没有,或者需要它可以调整大小,你需要添加一些编程约束或修改子视图的框架以适应。 我使用snapkit使事情变得简单:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
奖励积分
据称你可以使用prepareForInterfaceBuilder()
在Interface Builder中看到这些可重用的视图,但我没有太多运气。 此博客建议添加contentView
属性,并调用以下内容:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
你只需要在你的IB中拖放UIView
并将其插入并设置即可
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]
现在,您可以根据需要自定义视图。
File > New > New File > iOS > User Interface > View
File > New > New File > iOS > Source > CocoaTouch
loadNibNamed:
在NSBundle.mainBundle
上初始化xib及其关联文件,并且返回的第一个视图可以添加为loadNibNamed:
的子视图。 从笔尖加载的自定义视图可以保存到属性中,以便在viewDidLayoutSubviews
设置框架。 只需将框架设置为self.view
的框架,除非你需要使它小于self.view
。
class ViewController: UIViewController { weak var customView: MyView! override func viewDidLoad() { super.viewDidLoad() self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView self.view.addSubview(customView) addButtonHandlerForCustomView() } private func addButtonHandlerForCustomView() { customView.buttonHandler = { [weak self] (sender:UIButton) in guard let welf = self else { return } welf.buttonTapped(sender) } } override func viewDidLayoutSubviews() { self.customView.frame = self.view.frame } private func buttonTapped(button:UIButton) { } }
此外,如果要从xib与UIViewController
实例进行对话,请在自定义视图的类上创建弱属性。
class MyView: UIView { var buttonHandler:((sender:UIButton)->())! @IBAction func buttonTapped(sender: UIButton) { buttonHandler(sender:sender) } }
这是GitHub上的项目
多年来我一直在使用这段代码。 如果您计划在XIB中使用自定义类视图,请将其放在自定义类的.m文件中。
作为副作用,它会导致调用awakeFromNib,因此您可以将所有init / setup代码保留在那里。
- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
if ([[self subviews] count] == 0) {
UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.alpha = self.alpha;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
return view;
}
return self;
}
早期回归@brandonscript的想法更加流畅一点:
override func awakeFromNib() {
guard let xibName = xibName,
let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
let views = xib as? [UIView] else {
return
}
if views.count > 0 {
self.addSubview(views[0])
}
}
虽然我不推荐您要关闭的路径,但可以通过在希望显示视图的位置放置“嵌入式视图控制器视图”来完成。
嵌入包含单个视图的视图控制器 - 您要重用的视图。
在这一段时间已经有一段时间了,我已经看到了许多答案。 我最近重新访问它,因为我刚刚使用UIViewController嵌入。 这有效,直到你想在“运行时重复的元素”(例如UICollectionViewCell或UITableViewCell)中添加一些东西。 @TomSwift提供的链接让我遵循了这种模式
A)不要让父视图类成为自定义类类型,而是让FileOwner成为目标类(在我的例子中,CycleControlsBar)
B)嵌套小部件的任何插座/动作链接都是这样的
C)在CycleControlsBar上实现这个简单的方法:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
self.addSubview(container)
container.constrainToSuperview() // left as an excise for the student
}
}
像魅力一样工作,比其他方法简单得多。
这是你一直想要的答案。 您可以创建CustomView
类,在xib中包含所有子视图和出口的主实例。 然后,您可以将该类应用于故事板或其他xib中的任何实例。
无需摆弄文件所有者,或将插座连接到代理或以特殊方式修改xib,或添加自定义视图的实例作为其自身的子视图。
这样做:
UIView
更改为NibView
(或从UITableViewCell
更改为NibTableViewCell
) 而已!
它甚至可以与IBDesignable一起在故事板中的设计时渲染自定义视图(包括xib中的子视图)。
您可以在此处阅读更多相关信息: https : //medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
你可以在这里获得开源的BFWControls框架: https : //github.com/BareFeetWare/BFWControls
这里有一个驱动它的代码的简单摘录,以防你好奇: https : //gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
汤姆👣
“正确”的答案是,您并不打算使用相应的笔尖制作可重复使用的视图。 如果视图子类作为可重用对象很有价值,那么它很少需要一个笔尖来使用它。 以UIKit提供的每个视图子类为例。 这种想法的一部分是一个视图子类,实际上很有价值,不会使用nib实现,这是Apple的一般视图。
通常当您在笔尖或故事板中使用视图时,您仍然希望以图形方式针对给定的用例进行调整。
您可以考虑使用“复制粘贴”来重新创建相同或类似的视图,而不是制作单独的笔尖。 我相信它可以达到相同的要求,它会让你或多或少地与苹果公司的做法保持一致。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.