如何在故事板场景中embedded自定义视图xib?

我在XCode / iOS世界比较新, 我已经做了一些体积大小的故事板的应用程序,但我并没有削减整个笔尖/西克事情的牙齿。 我想使用相同的场景工具来devise/布局可重用的视图/控件。 所以我为我的视图子类创build了我的第一个xib并绘制了它:

在这里输入图像说明

我有我的网点连接和约束设置,就像我习惯做的故事板。 我将我的File Owner的类设置为我的自定义UIView子类的类。 所以我假设我可以用一些API来实例化这个视图子类,并且将如图所示configuration/连接。

现在回到我的故事板,我想embedded/重用这个。 我在表格视图原型单元格中这样做:

在这里输入图像说明

我有一个看法。 我已经把它的类设置为我的子类。 我已经创build了一个出口,所以我可以操纵它。

64美元的问题是我在哪里/如何表明仅仅把一个空的/未configuration的视图子类的实例放在那里是不够的,而是使用我创build的.xib来configuration/实例化呢? 这将是非常酷,如果在XCode6中,我可以只input用于给定的UIView的XIB文件,但我没有看到一个领域,所以我假设我必须在代码的地方做一些事情。

(我确实看到过类似的其他问题,但是还没有发现任何问题,或者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样子: 在这里输入图像说明

蓝色空间基本上是一个UIView,它将“保留”你的xib。

这是你的xib:

在这里输入图像说明

有一个动作连接到一个button,将打印一些文本。

这是最后的结果:

在这里输入图像说明

第一个clickMe和第二个之间的区别在于第一个被添加到使用StoryBoardUIViewController 。 第二个是使用代码添加的。

你需要在自定义的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; } 

这里有一个相当不错的书面讨论这个技术和几个select。

此外,如果将IB_DESIGNABLE添加到@interface声明中,并提供一个initWithFrame:方法,则可以使用IB(需要Xcode 6)进行devise时预览:

 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 3(Swift 2是类似的,但你必须自己翻新):

  1. 像这样创build一个新的类:

     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 views = xib as? [UIView], views.count > 0 else { return } self.addSubview(views[0] as! UIView) } } 
  2. 在故事板中,创build一个将放置.xib文件的视图。 给它一个XibView的类名:

  3. 在新的XibView的属性检查器中,设置.xib的名称(不带文件扩展名):

有一件事要注意:这是假设你将.xib框架与其容器相匹配。 如果你不这样做,或者需要resize,你需要添加一些编程约束或修改子视图的框架。 我使用snapkit使事情变得简单:

 xibView.snp_makeConstraints(closure: { (make) -> Void in make.edges.equalTo(self) }) 
  • 创buildxib文件File > New > New File > iOS > User Interface > View
  • 创build自定义的UIView类File > New > New File > iOS > Source > CocoaTouch
  • 将xib文件的标识分配给自定义视图类
  • 在视图控制器的viewDidLoad中,使用loadNibNamed:来初始化xib及其关联文件loadNibNamed: on NSBundle.mainBundle并返回的第一个视图可以作为loadNibNamed:的子视图添加。
  • 从nib加载的自定义视图可以保存到一个属性,用于在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实例,那么在自定义视图的类上创build一个弱属性。

     class MyView: UIView { var buttonHandler:((sender:UIButton)->())! @IBAction func buttonTapped(sender: UIButton) { buttonHandler(sender:sender) } } 

这是GitHub上的项目

你只需要拖放UIView到你的IB中,然后将其放置并设置

 yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView] 

在这里输入图像说明

  1. 添加新文件=>用户界面=> UIView
  2. 设置自定义类 – yourUIViewClass
  3. 设置恢复ID – yourUIViewClass
  4. yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

现在您可以自定义视图。

“正确”的答案是,你并不是要用相应的笔尖来制作可重复使用的视图。 如果一个视图子类作为一个可重用的对象是有价值的,那么它很less需要一个笔尖来处理它。 以UIKit提供的每个视图子类为例。 这个想法的一部分是一个视图子类,实际上是有价值的不会用一个笔尖实现,这是苹果公司的一般看法。

通常,当您在笔尖或故事板中使用视图时,无论如何您都会想要针对给定的用例对graphics进行调整。

您可以考虑使用“复制粘贴”来重新创build相同或相似的视图,而不是制作单独的笔尖。 我相信这个要求能够达到同样的要求,并且可以让你或多或less地与苹果公司正在做的事情保持一致。

@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]) } } 

我已经使用这段代码多年了。 如果您打算在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; }