我在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;
}
这是否像它那样容易?或者我为此工作太辛苦了?
答案 0 :(得分:26)
你几乎就在那里。您需要在分配视图的自定义类中覆盖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就会知道将xib加载到UIView中。
这里有一个更详细的解释:
这就是你的UIViewController
在你的故事板上的样子:
蓝色空间基本上是一个UIView,它将"持有"你的xib。
这是你的xib:
有一个Action连接到其上的按钮,将打印一些文本。
这是最终结果:
第一次点击和第二次点击之间的区别在于第一次点击是使用UIViewController
添加到StoryBoard
。第二个是使用代码添加的。
答案 1 :(得分:16)
您需要在自定义awakeAfterUsingCoder:
子类中实现UIView
。此方法允许您将解码的对象(来自故事板)与不同的对象(来自可重用的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;
}
有一篇非常好的文章here讨论了这种技术和一些替代方案。
此外,如果您在@interface声明中添加IB_DESIGNABLE
并提供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;
}
答案 2 :(得分:15)
这种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字段中设置.xib的名称(不带文件扩展名):
将新的Xib视图添加到项目中,并在属性检查器中设置Xib"文件的所有者"到XibView
(确保您只将#34;文件的所有者"设置为自定义类,不要将内容视图子类化,否则会崩溃),再次,设置IBInspectable字段:
有一点需要注意:这假设您将.xib框架与其容器匹配。如果您没有,或者需要它可以调整大小,您需要添加一些编程约束或修改子视图的框架以适应。我使用snapkit来简化:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
加分
据称,您可以使用prepareForInterfaceBuilder()
在Interface Builder中显示这些可重复使用的视图,但我没有太多运气。 This blog建议添加contentView
属性,并调用以下内容:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
答案 3 :(得分:2)
您只需将UIView
拖放到您的IB中并将其插入并设置
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]
现在您可以根据需要自定义视图。
答案 4 :(得分:2)
File > New > New File > iOS > User Interface > View
File > New > New File > iOS > Source > CocoaTouch
loadNibNamed:
上的NSBundle.mainBundle
初始化xib及其关联文件,并且可以将返回的第一个视图添加为self.view的子视图。可以将从笔尖加载的自定义视图保存到属性,以便在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
上的项目答案 5 :(得分:2)
我多年来一直在使用此代码段。如果您计划在XIB中使用自定义类视图,请将其放在自定义类的.m文件中。
作为副作用,它会导致调用awakeFromNib,因此您可以将所有初始化/设置代码留在那里。
- (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;
}
答案 6 :(得分:1)
早期回归@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])
}
}
答案 7 :(得分:0)
虽然我不建议您沿路径走,但可以通过在要显示该视图的位置放置一个“嵌入式视图控制器视图”来完成。
嵌入包含单个视图的视图控制器-您要重用的视图。
答案 8 :(得分: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
}
}
像魅力一样工作,并且比其他方法简单得多。
答案 9 :(得分:0)
这是您一直想要的答案。您可以只创建CustomView
类,并将其主实例包含在带有所有子视图和出口的xib中。然后,您可以将该类应用于情节提要或其他xib中的任何实例。
无需摆弄File的所有者,或将插座连接到代理,也无需以特殊方式修改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
汤姆
答案 10 :(得分:-2)
"正确"答案是,您并不打算使用相应的笔尖制作可重复使用的视图。如果视图子类作为可重用对象很有价值,那么它很少需要一个笔尖来使用它。以UIKit提供的每个视图子类为例。这种想法的一部分是一个视图子类,它实际上很有价值,不会使用nib实现,这是Apple的一般视图。
通常当您在笔尖或故事板中使用视图时,无论如何都需要以图形方式对其进行调整。
您可以考虑使用"复制粘贴"用于重新创建相同或类似的视图,而不是制作单独的笔尖。我相信它可以达到相同的要求,它会让你或多或少地与苹果公司的行为保持一致。