Ios 从序列图像板中的外部xib文件加载视图

Ios 从序列图像板中的外部xib文件加载视图,ios,uiview,storyboard,xib,Ios,Uiview,Storyboard,Xib,我希望在故事板中的多个ViewController中使用一个视图。因此,我考虑在外部xib中设计视图,以便在每个viewcontroller中反映更改。但如何从故事板中的外部xib加载视图,甚至可能吗?如果不是这样的话,还有什么其他的替代方案可以满足关于VE的情况呢?目前最好的解决方案是只使用自定义视图控制器,其视图定义在xib中,并在向其添加视图控制器时删除Xcode在情节提要中创建的“视图”属性(但不要忘记设置自定义类的名称) 这将使运行时自动查找并加载xib。您可以对任何类型的容器视图或内

我希望在故事板中的多个ViewController中使用一个视图。因此,我考虑在外部xib中设计视图,以便在每个viewcontroller中反映更改。但如何从故事板中的外部xib加载视图,甚至可能吗?如果不是这样的话,还有什么其他的替代方案可以满足关于VE的情况呢?

目前最好的解决方案是只使用自定义视图控制器,其视图定义在xib中,并在向其添加视图控制器时删除Xcode在情节提要中创建的“视图”属性(但不要忘记设置自定义类的名称)


这将使运行时自动查找并加载xib。您可以对任何类型的容器视图或内容视图使用此技巧。

即使您的类与xib名称不同,也可以使用此解决方案。 例如,如果您有一个基本视图控制器类controllerA,该类具有XIB名称controllerA.XIB,并且您将其子类化为controllerB,并希望在情节提要中创建controllerB的实例,那么您可以:

  • 在情节提要中创建视图控制器
  • 将控制器的类别设置为controllerB
  • 删除情节提要中控制器B的视图
  • 将controllerA中的负荷视图替代为:
*

我的完整示例是,但我将在下面提供一个摘要

布局

向项目中添加一个分别具有相同名称的.swift和.xib文件。该.xib文件包含自定义视图布局(最好使用自动布局约束)

使swift文件成为xib文件的所有者

代码

将以下代码添加到.swift文件中,并连接.xib文件中的出口和操作

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}
使用它

在故事板中的任意位置使用自定义视图。只需添加一个
UIView
,然后将类名设置为自定义类名


假设您已经创建了要使用的xib:

1) 创建UIView的自定义子类(您可以转到File->New->File…->Cocoa Touch类。确保“subclass of:”是“UIView”)

2) 在初始化时将基于xib的视图作为子视图添加到此视图

在Obj-C中

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}
斯威夫特2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}
斯威夫特3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}
3) 无论您想在故事板中使用它,都可以像平常一样添加UIView,选择新添加的视图,转到Identity Inspector(右上角的第三个图标,看起来像一个带线条的矩形),然后在“Custom Class”(自定义类)下以“Class”(类)的形式输入子类的名称。

我总是找到“add it as a subview”(将其添加为子视图)解决方案不符合要求,因为它与(1)自动布局、(2)IBInspectable和(3)插座一起拧紧。相反,让我向您介绍
awakafter:
,一种
NSObject
方法的魔力

awakafter
允许您将实际从笔尖/情节提要中唤醒的对象与完全不同的对象交换出去。然后将该对象放入水合过程,对其调用
awakeFromNib
,并将其添加为视图,等等

我们可以在视图的“Carboard cut-out”子类中使用它,它的唯一目的是从NIB加载视图并将其返回以在故事板中使用。然后,可嵌入的子类在情节提要视图的标识检查器中指定,而不是在原始类中指定。它实际上不必是一个子类才能工作,但使它成为一个子类是允许IB查看任何IBInspectable/IBOutlet属性的

这个额外的样板文件可能看起来不太理想,从某种意义上说是这样,因为理想情况下,
UIStoryboard
可以无缝地处理这个问题,但它的优点是完全不修改原始NIB和
UIView
子类。它所扮演的角色基本上是适配器或桥接类,作为一个附加类,它在设计上是完全有效的,即使它令人遗憾。另一方面,如果您希望节约类,@BenPatch的解决方案通过实现一个带有其他一些小改动的协议来工作。哪种解决方案更好的问题可以归结为程序员风格的问题:是喜欢对象组合还是多重继承

注意:NIB文件中视图上设置的类保持不变。可嵌入子类仅在情节提要中使用。子类不能用于在代码中实例化视图,因此它本身不应该有任何附加逻辑。它应该只包含
awakafter
hook

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}
⚠️ 这里一个显著的缺点是,如果在情节提要中定义了与其他视图无关的宽度、高度或纵横比约束,则必须手动复制这些约束。与两个视图相关的约束安装在最近的公共祖先上,视图从内到外从情节提要中唤醒,因此当这些约束在superview上水合时,交换已经发生。仅涉及相关视图的约束直接安装在该视图上,因此在交换时会被抛出,除非复制它们

请注意,此处发生的情况是,脚本中视图上安装的约束被复制到新实例化的视图中,该视图可能已经有自己的约束,这些约束在其nib文件中定义。这些都不受影响

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  
instantialeviewfromnib
是对
UIView
的类型安全扩展。它所做的只是循环遍历NIB的对象,直到找到一个与该类型匹配的对象。请注意,泛型类型是返回值,因此必须在调用站点指定类型

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
扩展UIView{ 公共类func实例化eviewFromnib(unibName:String,inBundle bundle:bundle=bundle.main)->T{ 如果let objects=bundle.loadNibNamed(nibName,所有者:nil){ 对象一
extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}
@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupFromNib()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupFromNib()
    }

}
@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end
#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end
#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end
MyView* view = [[MyView alloc] init];