iOS-自定义View

159 阅读4分钟

一.纯代码自定义View

1. 首先在initWithFrame:方法中将需要的子控件加入view中

注意,这里只是加入到view中,并没有设置各个子控件的尺寸。

  • 为什么要在initWithFrame:方法而不是在init方法? 因为使用纯代码的方式自定义View,在以后使用的时候可能使用init方法创建,也有可能使用initWithFrame:方法创建,但是无论哪种方式,最后都会调用到initWithFrame:方法。在这个方法中创建子控件,可以保证无论哪种方式都可以成功创建。

  • 为什么要在initWithFrame:方法里面只是将子控件加到view而不设置子控件尺寸?

前面已经说过,两种方式最后都会调用到initWithFrame:方法。如果使用init方法创建,那么这个view的frame有可能是不确定的,

CYLView *view = [[CYLView alloc] init];
view.frame = CGRectMake(0,0, 100,100);

如果是这种情况,那么在init方法中,frame是不确定的,此时如果在initWithFrame:方法中设置子控件的尺寸,那么各个子控件的尺寸都会是0,因为这个view的frame还没有设置。(可以看到是在发送完init消息才设置的)。 所以我们应该保证view的frame设置完才会设置它的子控件的尺寸。 所以在layoutSubviews设置内部控件的frame。

2. 在layoutSubviews设置内部控件的frame

view第一次将要显示的时候会调用这个方法,之后当view的尺寸(不是位置)改变时,会调用这个方法。

简单的自定义一个ShopView里面有个Shop模型,代码如下:

Shop模型.h文件

#import <Foundation/Foundation.h>  
@interface Shop : NSObject  
   
@property (nonatomic, copy) NSString *icon;   //商品的图标
@property (nonatomic, copy) NSString *name;   //商品的名称
// 提供构造方法  
- (instancetype)initWithDict:(NSDictionary *)dict;  
+ (instancetype)shopWithDict:(NSDictionary *)dict;  
  
@end  

Shop模型.m文件

#import "Shop.h"  
@implementation Shop  

//对象方法
- (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 

ShopView.h文件

#import <UIKit/UIKit.h>  
@class Shop;  
@interface ShopView : UIView  
  
// 商品模型
@property (nonatomic, strong) Shop *shop;  
// 构造方法  
- (instancetype)initWithShop: (Shop *)shop;  
+ (instancetype)shopViewWithShop: (Shop *)shop;  
  
@end  

ShopView.m

#import "ShopView.h"  
#import "Shop.h"  
@interface ShopView ()  

@property (nonatomic, weak) UIImageView *iconView;  //图片控件
@property (nonatomic, weak) UILabel *titleLabel;    //标题控件 

@end  
@implementation ShopView  
  
//初始化子控件(不要设置frame) 
- (instancetype)init{  
    if (self = [super init]) {  
        [self setUp];  
    }  
    return self;  
}  
  
  
//注意: 创建对象用[[xxx alloc]init]方法和[[xxx alloc]initWithFrame]:方法都会调用initWithFrame: 
- (instancetype)initWithFrame:(CGRect)frame{  
    if (self =[super initWithFrame:frame]) {  
        [self setUp];  
    }  
    return self;  
}  
  
//对象方法
- (instancetype)initWithShop:(XMGShop *)shop{  
    if (self = [super init]) {  
        // 注意:先创建后赋值  
        [self setUp];  
        self.shop = shop;  
    }  
    return self;  
}  
  
//类方法
+ (instancetype)shopViewWithShop:(XMGShop *)shop{  
    return [[self alloc] initWithShop:shop];  
}  
  
// 初始化 
- (void)setUp{  
    // 1.创建商品的UIImageView对象  
    UIImageView *iconView = [[UIImageView alloc] init];  
    iconView.backgroundColor = [UIColor blueColor];  
    [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  
    [super layoutSubviews];  
    // 1.获取当前控件的尺寸  
    CGFloat width = self.frame.size.width;  
    CGFloat height = self.frame.size.height;  
    // 2.设置子控件的frame  
    self.iconView.frame = CGRectMake(0, 0, width, width);  
    self.titleLabel.frame = CGRectMake(0, width, width, height - width);  
}  
  
// 重写set方法:只要外边传数据就会调用 
- (void)setShop:(Shop *)shop{  
    _shop = shop;  
    // 设置数据  
    self.iconView.image = [UIImage imageNamed:shop.icon];  
    self.titleLabel.text = shop.name;  
}  
@end 

二. 使用xib自定义View

  1. 创建xib,在xib中拖入需要添加的控件并设置好尺寸。并且要将这个xib的Class设置为我们的自定义类,通过IBOutlet的方式,将xib中的控件与自定义类进行关联。
  2. 使用xib的方式可以省去initWithFrame:和layoutSubviews中添加子控件和设置子控件尺寸的步骤,以及省略在viewController里面设置view的frame的过程,因为添加子控件和设置子控件的尺寸以及整个view的尺寸在xib中就已经完成(注意整个view的位置还没有设置,需要在viewController里面设置)。
  3. 我们只需对外提供数据接口,重写setter方法就可以显示数据
  4. 建议在awakeFromNib方法中进行初始化的额外操作,因为awakeFromNib是在初始化完成后调用,所以在这个方法里面访问属性(IBOutlet)就可以保证不为nil。
  • 使用xib自定义view后一定会调下面两个方法:
//因为xib文件就是本地的一个文件所以要进行解档操作,那肯定调这个方法啦
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super initWithCoder:aDecoder];
    if (self) { 
    }
    return self;
}
//从xib初始化完会调用,所以建议在awakeFromNib方法中进行初始化的额外操作
- (void)awakeFromNib{
    [super awakeFromNib];
}

代码如下: ShopView.h

#import <UIKit/UIKit.h>  
@class Shop;  
@interface ShopView : UIView  

@property (nonatomic, strong) Shop *shop;  //数据模型
+ (instancetype)shopView;  // 快速构造方法  

@end  

ShopView.m

#import "ShopView.h"  
#import "Shop.h"  
  
@interface ShopView ()  
@property (weak, nonatomic) IBOutlet UIImageView *iconView;  
@property (weak, nonatomic) IBOutlet UILabel *titlelabel;  
@end  
  
@implementation ShopView  
  
+ (instancetype)shopView{  
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] firstObject];  
}  

- (void)setShop:(Shop *)shop{  
    _shop = shop;  
    // 设置数据  
    self.iconView.image = [UIImage imageNamed:shop.icon];  
    self.titleLabel.text = shop.name;  
}  

//- (void)awakeFromNib
@end