在 .xib 中使用自定义 UIView 作为 IBOutlet

Posted

技术标签:

【中文标题】在 .xib 中使用自定义 UIView 作为 IBOutlet【英文标题】:Use Custom UIView as IBOutlet in .xib 【发布时间】:2019-10-18 10:20:09 【问题描述】:

我有一个继承自 UIView 的自定义类

@interface StatusBarView : UIView

@property (weak, nonatomic) id <ActivationStatusDelegate> delegate;


//MARK: init
- (id) initWithCustom: (struct WidgetCustom *) widget;
- (id) initWithStatus:(ActivationBtnStatus) activeStatus;

//MARK: Function
- (void) setStatus: (ActivationBtnStatus) status;
@end

这是实现的一部分

@interface StatusBarView ()
@property (strong, nonatomic) IBOutlet UIButton *button1;
@property (strong, nonatomic) IBOutlet UIButton *button2;

- (void) createDefaultWidget;

@end

@implementation StatusBarView

ActivationBtnStatus status = noStatus;
struct WidgetCustom widget;
bool isWidgetSet = false;
- (void)awakeFromNib 
    [super awakeFromNib];
    if (!isWidgetSet) 
        [self createDefaultWidget];
    
    [self createButton];

- (instancetype)initWithCoder:(NSCoder *)coder

    self = [super initWithCoder:coder];
    if (self) 

        [self createDefaultWidget];
        [self createButton];
    
    return self;


- (void)layoutSubviews 
    CAShapeLayer * maskLayer1 = [CAShapeLayer layer];
    maskLayer1.path = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners: UIRectCornerBottomRight | UIRectCornerTopRight cornerRadii: (CGSize)10.0, 10.].CGPath;
    CAShapeLayer * maskLayer2 = [CAShapeLayer layer];
    maskLayer2.path = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners: UIRectCornerBottomLeft | UIRectCornerTopLeft cornerRadii: (CGSize)10.0, 10.].CGPath;

    _button1.layer.mask = maskLayer1;
    _button2.layer.mask = maskLayer2;




- (id)initWithCustom:(struct WidgetCustom *) widget 
    self = [[[NSBundle mainBundle] loadNibNamed:@"ActivationStatus" owner:nil options:nil] lastObject];

    if (self) 
        isWidgetSet = true;
        widget = widget;
    

    return self;


- (id)initWithStatus:(ActivationBtnStatus)activeStatus 
    self = [[[NSBundle mainBundle] loadNibNamed:@"ActivationStatus" owner:nil options:nil] lastObject];

    if (self) 
        status = activeStatus;
    

    return self;


这就是我管理 .xib 文件的方式 文件所有者的类是空的,没有出路

插座改为连接到视图,我为 .xib 文件的视图设置 StatusBarView 类,如下所示

现在在不同的 ViewController 中,我想将此类用作像这样的 IBOutlet,而不需要任何更多的初始化:

但结果只是一个灰色的视图。 有可能做这样的事情吗?如果是,请告诉我哪里出错了?

【问题讨论】:

有可能,但不是你这样做的方式。不要对self 进行评价并使用owner @Sulthan 你能解释更多吗?我应该在哪里进行此更改? @taratandel - 这看起来是一个非常好的分步教程:medium.com/@brianclouser/… ... 如果您想在 Storyboard 中布局视图时查看 xib 的按钮/内容,你还需要实现IBDesignable @taratandel 简而言之,您不能从StatusBarView 的初始化程序加载类StatusBarView。相反,您通常使用 self = [super init...] 创建 self,然后从 nib 加载 view 并将其添加为 StatusBarView 实例的子视图。这意味着您必须将 self 作为所有者传递,并且在 xib 中,容器视图的类型为 UIView,所有者的类型为 StatusBarView @DonMag 感谢本教程正是我所需要的。 【参考方案1】:

从 XIB 加载自定义视图并不像看起来那么简单。我为此创建了一个自定义类,其中包含一堆方便的方法。因此,要完成这项工作,您必须让您的自定义视图从此类继承并为其设置 bundle 属性,默认情况下,您的类名应与 XIB 名称相同

界面

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface MLSXibLoadingView : UIView

- (instancetype)initWithWidth:(CGFloat)width;

- (instancetype)initWithHeight:(CGFloat)height;

- (instancetype)initWithXibClass:(Class)xibClass;

- (instancetype)initFromXib;

- (instancetype)commonInit;

- (void)setup;

- (void)layoutDone;

- (void)localize;

- (void)update;

- (NSLayoutConstraint *)constraintWithIdentifier:(NSString *)identifier;

- (void)setConstant:(CGFloat)constant forConstraintWithIdentifier:(NSString *)identifier;

@property (nonatomic) NSBundle *bundle;
@property (nonatomic) BOOL layoutIsDone;

@end

实施

#import "MLSXibLoadingView.h"


@implementation MLSXibLoadingView

- (instancetype)init

    self = [super init];
    if (self) 
        self = [self commonInit];
    
    return self;


- (instancetype)initWithFrame:(CGRect)frame

    self = [super initWithFrame:frame];
    if (self) 
        self = [self commonInit];
    
    return self;


- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder

    if (!self.subviews.count) 
        return [self commonInit];
    

    return self;


- (void)layoutSubviews

    [super layoutSubviews];

    if (!_layoutIsDone) 
        _layoutIsDone = YES;
        [self layoutDone];
    


- (instancetype)initWithWidth:(CGFloat)width

    self = [self commonInitAssigningFrame:NO];
    self.frame = [MLSUtils rect:self.frame scaledToWidth:width];
    return self;


- (instancetype)initWithHeight:(CGFloat)height

    self = [self commonInitAssigningFrame:NO];
    self.frame = [MLSUtils rect:self.frame scaledToHeight:height];
    return self;


- (instancetype)initWithXibClass:(Class)xibClass

    return [self commonInitAssigningFrame:NO xibClass:xibClass];


- (instancetype)initFromXib

    return [self commonInitAssigningFrame:NO];


- (instancetype)commonInit

    return [self commonInitAssigningFrame:YES];


- (void)setup



- (void)layoutDone



- (void)localize



- (void)update



- (NSBundle *)bundle

    return nil; // implement in subclasses


- (instancetype)commonInitAssigningFrame:(BOOL)assignFrame

    return [self commonInitAssigningFrame:assignFrame xibClass:self.class];


- (instancetype)commonInitAssigningFrame:(BOOL)assignFrame xibClass:(Class)xibClass

    NSBundle *b = [self bundle];
    assert(b);

    NSString *xibName = NSStringFromClass(xibClass);
    assert([b pathForResource:xibName ofType:@"nib"]);

    MLSXibLoadingView *xibView = [b loadNibNamed:xibName owner:nil options:nil][0];
    xibView.frame = assignFrame ? self.frame : xibView.frame;
    xibView.autoresizingMask = self.autoresizingMask;
    xibView.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;

    for (NSLayoutConstraint *constraint in self.constraints) 
        id firstItem = constraint.firstItem;

        if (firstItem == self) 
            firstItem = xibView;
        

        id secondItem = constraint.secondItem;

        if (secondItem == self) 
            secondItem = xibView;
        

        NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:firstItem
                                                                         attribute:constraint.firstAttribute
                                                                         relatedBy:constraint.relation
                                                                            toItem:secondItem
                                                                         attribute:constraint.secondAttribute
                                                                        multiplier:constraint.multiplier
                                                                          constant:constraint.constant];

        newConstraint.priority = constraint.priority;
        newConstraint.identifier = constraint.identifier;

        [xibView addConstraint:newConstraint];
    

    return xibView;


- (NSLayoutConstraint *)constraintWithIdentifier:(NSString *)identifier

    for (NSLayoutConstraint *constraint in self.constraints) 
        if ([constraint.identifier isEqualToString:identifier]) 
            return constraint;
        
    

    return nil;


/**
 * Use this method to change constraint constant with given identifier because constraints outlets get invalid
 * after view exchange in commonInitAssigningFrame: method
 */
- (void)setConstant:(CGFloat)constant forConstraintWithIdentifier:(NSString *)identifier

    for (NSLayoutConstraint *constraint in self.constraints) 
        if ([constraint.identifier isEqualToString:identifier]) 
            constraint.constant = constant;
            break;
        
    


@end

【讨论】:

以上是关于在 .xib 中使用自定义 UIView 作为 IBOutlet的主要内容,如果未能解决你的问题,请参考以下文章

如何将 UITableView 作为子视图添加到 xib 中的 UIView?

自定义 UIView(.xib) 在 UITableViewCell 中添加为子视图后自动更改框架

使用 XIB 创建自定义 UIView

将 xib 中的子视图添加到自定义 UIView

使用自定义 uiview xib

在自包含的自定义 UIView 子类中使用 .xib 文件进行原型设计