Ios 从序列图像板中的外部xib文件加载视图
我希望在故事板中的多个ViewController中使用一个视图。因此,我考虑在外部xib中设计视图,以便在每个viewcontroller中反映更改。但如何从故事板中的外部xib加载视图,甚至可能吗?如果不是这样的话,还有什么其他的替代方案可以满足关于VE的情况呢?目前最好的解决方案是只使用自定义视图控制器,其视图定义在xib中,并在向其添加视图控制器时删除Xcode在情节提要中创建的“视图”属性(但不要忘记设置自定义类的名称)Ios 从序列图像板中的外部xib文件加载视图,ios,uiview,storyboard,xib,Ios,Uiview,Storyboard,Xib,我希望在故事板中的多个ViewController中使用一个视图。因此,我考虑在外部xib中设计视图,以便在每个viewcontroller中反映更改。但如何从故事板中的外部xib加载视图,甚至可能吗?如果不是这样的话,还有什么其他的替代方案可以满足关于VE的情况呢?目前最好的解决方案是只使用自定义视图控制器,其视图定义在xib中,并在向其添加视图控制器时删除Xcode在情节提要中创建的“视图”属性(但不要忘记设置自定义类的名称) 这将使运行时自动查找并加载xib。您可以对任何类型的容器视图或内
这将使运行时自动查找并加载xib。您可以对任何类型的容器视图或内容视图使用此技巧。即使您的类与xib名称不同,也可以使用此解决方案。 例如,如果您有一个基本视图控制器类controllerA,该类具有XIB名称controllerA.XIB,并且您将其子类化为controllerB,并希望在情节提要中创建controllerB的实例,那么您可以:
- 在情节提要中创建视图控制器
- 将控制器的类别设置为controllerB
- 删除情节提要中控制器B的视图
- 将controllerA中的负荷视图替代为:
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];