美文网首页iOSiOS、swift技术交流!iOS 艾欧艾斯
使用代码自定义UIView注意一二三

使用代码自定义UIView注意一二三

作者: CoderAO | 来源:发表于2015-05-28 21:20 被阅读12489次

当一撮样式一样的视图在工程中被多次使用的时候,为了方便使用,我们会想把他们抽成一个单独的类,进行视图的自定义.

比如我们要做一个这样的东西:


beauty.png

这一块由两个东西组成:一个imageView和一个label。首先我们新建一个继承自UIView的类MyView.

在MyView的.m文件里,你可以根据自己的意愿将两个子控件设置成MyView的属性或者成员变量,这里我们设置为属性。

@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *label;

那接下来,就是要向自定义的view里面添加控件咯。
通常的思路是重写UIView的构造方法。那么这里要说第一个注意了:

1.要重写UIView的initWithFrame:方法而不是init方法

为什么呢?因为当外部调用init的方法的时候,其内部也会默默地调用initWithFrame:方法,你不能保证别的同事在调用你的类的时候不会直接调用initWithFrame:方法,这时如果你仅重写了init方法,那么两个子控件便无从创建.

于是我们写成这样:

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        /* 添加子控件的代码*/
    }
    return self;
}

接下开始添加子控件,不知道还会不会有小伙伴是这样写的:

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.imageView = [[UIImageView alloc]init];
        self.imageView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.width);
        [self addSubview:self.imageView];
    }
    return self;
}

这样有什么问题吗?如果全部写好运行起来,能看到imageView吗?答案是不确定的.问题出现在给imageView的frame赋值那里.
imageView的宽和高直接用self.frame.size.width,但这个时候self.frame可能是没有值的.

上面我们说过,如果外部调用了MyView的init方法,也会执行到这里,这时候frame还没有赋值.

所以第二个注意:

2.不要在构造方法里面直接取自身(self,或者说本视图)的宽高,这时候取到的宽高是不准的.

我想初学自定义tableViewCell的小伙伴都遇到过类似这样的问题:
重写cell的初始化方法向cell内添加子控件时
(假设cell的高度设为100,想要添加一个label在cell的底部),

于是这样写:
label.frame = CGRectMake(0,self.frame.size.height - 20, 100, 20),

运行出来却发现添加的label并不在我们期望的位置(底部),
而是在cell比较偏上的位置(实际y的值是44-20而不是100-20).

然后在debug的时候发现:虽然cell的高度已经设定成为100,但在初始化方法里面取到的cell的高度仍然是默认的44.

这其实也是刚才说的原因导致的:我们不能在控件的构造方法里面取其frame或者bounds,这时候取值是不准确的.

所以在重新构造方法的时候,我们只需要把控件放进去,暂时先不用考虑他们在什么位置:

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.imageView = [[UIImageView alloc]init];
        [self addSubview:self.imageView];

        self.label = [[UILabel alloc]init];
        self.label.textAlignment = NSTextAlignmentCenter;
        [self addSubview:self.label];
    }
    return self;
}

那么在什么时候设置子控件的frame呢?

第三个注意:

3.在layoutSubViews方法里面布局子控件

如下:

- (void)layoutSubviews {
    // 一定要调用super的方法
    [super layoutSubviews];

    // 确定子控件的frame(这里得到的self的frame/bounds才是准确的)
    CGFloat width = self.bounds.size.width;
    CGFloat height = self.bounds.size.height;
    self.imageView.frame = CGRectMake(0, 0, width, width);
    self.label.frame = CGRectMake(0, width, width, height - width);
}

这里要注意的就是需要在布局之前一定要先调用父类的layoutSubviews方法.

由于在这个方法里可以获取MyView准确的宽和高,我们直接取它的宽高来设置imageView和label的宽高就可以

当然,子控件的创建不一定要写在MyView的构造方法里面,既然声明成为属性,使用懒加载(重写属性的get方法)也是一个不错的选择.

相关文章

网友评论

  • 0x00chen:假如我是for循环创建的button就不能在layoutsubviews里面写frame了啊
    CoderAO:@0x00chen 用tag取出来赋值呢?
    0x00chen:@CoderAO 但是我的button是局部变量,怎么在layoutsubviews里面赋值呢?不是抬杠,我只是有点疑惑,
    CoderAO:用for循环创建button说明button是有规律的,在layoutsubviews里面同样可以用这个规律通过遍历来设置frame的吧
  • 狗狗臭鸡蛋:// 一定要调用super的方法
    [super layoutSubviews];
    为啥
    CoderAO:因为父类为了布局做了很多你不知道的事情呀
  • 0d167a1b6709:赞,写的不错
  • feng_dev:如果 子控件的 frame 是根据 屏幕宽度来计算的,那设置 子控件的frame 写在 init with frame 和 layout subviews 方法 有区别吗?
    搬码小能手:有区别,你可以通过打印,看看init with frame 和layout subviews的执行的先后顺序,至于其他区别,两天内我简书里详细解释。😁
  • 格调main:必须点赞, 原本只是知其然不知其所以然, 现在看了你的 觉得理解更深刻了
  • 爱喝农药de清凉:转一下,用作笔记,行么?
    爱喝农药de清凉:@我是搬砖小能手 嗯嗯,谨记
    搬码小能手:做技术,要有自己的一套理解,邯郸学步你懂的吧
  • GavinKang:写的不错,不过最后一句貌似应该是重写属性的 set 方法,set 是赋值, get 是取值
    47c0639a1a9a:@我是搬砖小能手 重写get不一定延时。但是在oc中,懒加载基本要重写get方法,所以说懒加载就是重写get方法,没什么毛病,但是重写get不一定是懒加载
    搬码小能手:@不想当科学家的程序员不是高富帅 懒加载就是重写get方法?虽然看起来像,懒加载是延时加载,重写set get其本质是方法,点方法。重写set get延时了吗?
    47c0639a1a9a:懒加载就是重写get方法
  • 蛮荒星域:必须点个赞,解决了一个困扰我好久的问题。这次理清了
  • thanksdanny:感谢!终于理解原因了!
  • 小凡凡520:good mark
  • 金玉游龙:所以第二个注意
    那么在什么时候设置子控件的frame呢?

    可以对外暴露一个再次设置的方法,等frame什么的都设置好了之后,
    在调用创建 make subviews
  • 9bfe700d122a:我说以前那些不准的值 是咋回事呢 :+1:

本文标题:使用代码自定义UIView注意一二三

本文链接:https://www.haomeiwen.com/subject/coirqttx.html