iOS 自定义view 纯代码 & xib

680 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

自定义View

自定义View的基本步骤
  • 重写 - (instancetype)initWithFrame方法,在此方法中创建并添加子控件。
  • 提供一个便利的构造方法,通常为 类方法,快速创建一个实例对象
  • 重写 - (void)layoutSubviews方法,在此方法中设置子控件的frame,一定要调用[super layoutSubviews]
  • 设置模型属性,在set方法中,给对应的子控件赋值。

设置数据的方案1:

#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface DukeShopView : UIView

//@property (nonatomic, weak, readonly) UIImageView *iconView;
//@property (nonatomic, weak, readonly) UILabel *titleLabel;

- (void)setIcon: (NSString *)icon;
- (void)setName: (NSString *)name;

@end
NS_ASSUME_NONNULL_END
#import "DukeShopView.h"

@interface DukeShopView ()
@property (nonatomic, weak) UIImageView *iconView;
@property (nonatomic, weak) UILabel *titleLabel;
@end

@implementation DukeShopView
// 初始化子控件(不要设置frame)
- (instancetype)init{
    if (self = [super init]) {
        // 1. 创建商品的UIImageView对象
        UIImageView *iconView = [[UIImageView alloc] init];
        iconView.backgroundColor = [UIColor redColor];
        [self addSubview:iconView];
        _iconView = iconView;
        // 2. 创建商品标题对象
        UILabel *titleLabel = [[UILabel alloc] init];
        titleLabel.backgroundColor = [UIColor yellowColor];
        titleLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:titleLabel];
        _titleLabel = titleLabel;
    }  
    return self;
    
}

// 布局子控件(可以拿到frame)
- (void)layoutSubviews
{
    [super layoutSubviews];
    // 1. 获取当前控件的尺寸
    CGFloat width = self.frame.size.width;
    CGFloat height = self.frame.size.height;
    
    // 2. 设置子控件的frame
    self.iconView.frame = CGRectMake(0, 0, width, height);
    self.titleLabel.frame = CGRectMake(0, width, width, height - width);
}

- (void)setIcon: (NSString *)icon{
    self.iconView.image = [UIImage imageNamed:icon];
}

- (void)setName: (NSString *)name{
    self.titleLabel.text = name;
}

@end

设置数据的方案2:


#import <UIKit/UIKit.h>

@class DukeShop;

NS_ASSUME_NONNULL_BEGIN

@interface DukeShopView : UIView

//@property (nonatomic, weak, readonly) UIImageView *iconView;

//@property (nonatomic, weak, readonly) UILabel *titleLabel;


//- (void)setIcon: (NSString *)icon;

//- (void)setName: (NSString *)name;


@property (nonatomic, strong) DukeShop *shop;


- (instancetype)initWithShop: (DukeShop *)shop;
+ (instancetype)shopViewWithShop: (DukeShop *)shop;

@end

NS_ASSUME_NONNULL_END


#import "DukeShopView.h"

#import "DukeShop.h"

@interface DukeShopView ()

@property (nonatomic, weak) UIImageView *iconView;
@property (nonatomic, weak) UILabel *titleLabel;

@end

@implementation DukeShopView

// 初始化子控件(不要设置frame)
//- (instancetype)init{
//    if (self = [super init]) {
//        [self setUp];
//    }
//    return self;
//}

//注意:创建对象用init方法和 initWithFrame: 都会调到这个方法里。
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        [self setUp];
    }
    return self;
}

- (instancetype)initWithShop: (DukeShop *)shop{
    if (self = [super init]) {
        // 注意:先创建后赋值
        [self setUp];
        // 赋值
        self.shop = shop;
    }
    return  self;
}

+ (instancetype)shopViewWithShop: (DukeShop *)shop{
    return [[self alloc] initWithShop:shop];
}

// 初始化
- (void)setUp{
    // 1. 创建商品的UIImageView对象
    UIImageView *iconView = [[UIImageView alloc] init];
    iconView.backgroundColor = [UIColor redColor];
    [self addSubview:iconView];
    _iconView = iconView;

    // 2. 创建商品标题对象
    UILabel *titleLabel = [[UILabel alloc] init];
    titleLabel.backgroundColor = [UIColor yellowColor];
    titleLabel.textAlignment = NSTextAlignmentCenter;
    [self addSubview:titleLabel];
    _titleLabel = titleLabel;
}

// 布局子控件(可以拿到frame)
- (void)layoutSubviews
{
    [super layoutSubviews];
    // 1. 获取当前控件的尺寸
    CGFloat width = self.frame.size.width;
    CGFloat height = self.frame.size.height;

    // 2. 设置子控件的frame
    self.iconView.frame = CGRectMake(0, 0, width, height);
    self.titleLabel.frame = CGRectMake(0, width, width, height - width);
}


//- (void)setIcon: (NSString *)icon{
//    self.iconView.image = [UIImage imageNamed:icon];
//}
//- (void)setName: (NSString *)name{
//    self.titleLabel.text = name;
//}

- (void)setShop:(DukeShop *)shop
{
    _shop = shop;
    self.iconView.image = [UIImage imageNamed:shop.icon];
    self.titleLabel.text = shop.name;
}

@end

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface DukeShop : NSObject
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *name;

//- (instancetype)initWithIcon:(NSString *)icon name:(NSString *)name;
//+ (instancetype)shopWithIcon:(NSString *)icon name:(NSString *)name;


- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)shopWithDict:(NSDictionary *)dict;

@end

NS_ASSUME_NONNULL_END

#import "DukeShop.h"
@implementation DukeShop

- (instancetype)initWithIcon:(NSString *)icon name:(NSString *)name{
    if(self = [super init]) {
        self.icon = icon;
        self.name = name;
    }
    return self;
}

+ (instancetype)shopWithIcon:(NSString *)icon name:(NSString *)name{
    return [[self alloc] initWithIcon:icon name:name];
}

- (instancetype)initWithDict:(NSDictionary *)dict{
    if(self = [super init]) {
        self.icon = dict[@"icon"];
        self.name = dict[@"name"];
    }
    return self;
}
+ (instancetype)shopWithDict:(NSDictionary *)dict{
    return [[self alloc] initWithDict:dict];
}
@end

View 的封装

如果一个view内部的子控件比较多,一般会考虑自定义一个view,把它内部子控件的创建屏蔽起来,不让外界关心。

外界可以传入对应的模型数据给view,view拿到模型数据后给内部的子控件设置对应的数据。

封装控件的基本步骤: 在initWithFrame: 方法中添加子控件,提供便利构造方法 在layoutSubViews方法中设置子控件的frame(一定要调用super的layoutSubviews)

增加模型属性,在模型属性set方法中设置数据到子控件上。

以上是纯代码实现的View的封装,有些麻烦。

开发中另一种常用的封装方式是Xib

Xib 自定义view

Xib和StoryBoard对比

  1. 共同点: 都用来描述软件界面 都用Interface Builder工具来编辑 本质都是转换成代码去创建控件

  2. 不同点: Xib是轻量级的,用来描述局部的UI界面 StoryBoard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系。

Xib的加载

方法1:

NSArray *views = [[NSBundle mainbundle] loadNibNamed:@"xib文件名" owner:nil options: nil] 

方法2:

UINib *nib = [UINib nibWithNibName:@"xib文件名" bundle:nil];
NSArray *views = [nib instantiateWithOwner:nil options: nil];

控件有两种创建方式
  • 通过代码创建

    • 初始化一定会调用 -(instancetype)initWithFrame:方法
  • 通过Xib\StoryBoard创建

    • 初始化不会调用 -(instancetype)initWithFrame:方法,只会调用-(instancetype)initWithCoder:方法
    • 初始化完成之后,回调用awakeFromNib方法

通过两种加载方式,可以发现:有时候我们希望在控件初始化时做一些初始化的操作,如添加子控件,设置属性等,这时候需要根据控件的加载方式来选择-(instancetype)initWithFrame:-(instancetype)initWithCoder:awakeFromNib三个方法中的哪个方法进行初始化。