[iOS] UI控件及布局的小知识们(1)

目录:

  1. cell生命周期
  2. masonry数组布局
  3. tableview中如何让行高自动被撑起来
  4. scrollView缓慢滑动到边界松手不走scrollViewDidEndDecelerating
  5. UIButton的EdgeInsets来调整图片文字位置
  6. UITableViewCell的子view如果超过了cell的边界,会导致点击穿透
  7. sizeToFit和sizeThatFits
  8. masonry和frame不要混用

1. cell生命周期

cell的生命周期可以参考https://www.jianshu.com/p/59b8bb25f16a

对于cell而言- (void)prepareForReuse是先于tableview的cellForIndexPath delegate的,当tableview第一次出现没有任何滑动的时候,prepareForReuse是不会被调用的,但是当你稍微滑动一丢丢,就会有prepareForReuse的调用了,但是这个时候没有任何cell出屏幕啊,所以其实tableview是有准备出来一两个备用cell在屏幕外池子里面的~

当tableview第一次出现的时候分别给cell的prepareForReuse以及delegate的三个方法(如下)打断点发现:

  • prepareForReuse没有被调用
  • cellForRowAtIndexPath和willDisplayCell成对调用(多余屏幕内个数的次数,例如12次,但屏幕只放的下9个)
  • didEndDisplayingCell也会被调用多次,屏幕内放得下9个cell的话,会回调didEndDisplayingCell给indexpath给10-12行的,所以其实tableview有提前准备cell

当cell要展示的时候,会回调table view的delegate willDisplayCell,然后当cell完全出屏幕的时候会回调didEndDisplayingCell

// delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self.tableview dequeueReusableCellWithIdentifier:@"Table1ViewCell"];
    return cell;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
}

我打断点看了一下didEndDisplayingCell的object会接着用于prepareForReuse,然后再willDisplayCell,地址都是一致的。


2. masonry数组布局

masonry可以给NSArray的view数组布局,让他们沿着一个轴均匀分布:

@implementation NSArray (MASAdditions)

- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;

- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;

但是这种只能view的宽度/高度都一样,看源码可以看到:

MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (axisType == MASAxisTypeHorizontal) {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.width.equalTo(prev);

所以其实上面的方法主要适用于固定spacing然后几个view平分空间,或者view的宽度(水平分布)/高度(竖直分布)固定,平分spacing,反正得是宽度/高度一致。

虽然上面的不是很实用,不过这个分类里面还有可以给数组加一样或者更新一样的constraint哦~
mas_makeConstraints、mas_updateConstraints、mas_remakeConstraints

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
    NSMutableArray *constraints = [NSMutableArray array];
    for (MAS_VIEW *view in self) {
        NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
        [constraints addObjectsFromArray:[view mas_makeConstraints:block]];
    }
    return constraints;
}

我们经常出现的场景其实是spacing固定,宽度/高度不一样的沿着某个轴布局,我们可以借鉴masonry数组的思想统一布局改写一个分类:

- (void)mas_live_updateDistributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
    MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (!tempSuperView) {
        return;
    }
    if (axisType == MASAxisTypeHorizontal) {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_updateConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.left.equalTo(prev.mas_right).offset(fixedSpacing);
                } else {//first one
                    make.left.equalTo(tempSuperView).offset(leadSpacing);
                }
                
                make.top.bottom.equalTo(tempSuperView);
            }];
            
            if (i == self.count - 1) {
                [tempSuperView mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.right.equalTo(v.mas_right).offset(tailSpacing);
                }];
            }
            prev = v;
        }
    }
    else {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_updateConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
                } else {//first one
                    make.top.equalTo(tempSuperView).offset(leadSpacing);
                }
                
                make.right.left.equalTo(tempSuperView);
            }];
            
            if (i == self.count - 1) {
                [tempSuperView mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.bottom.equalTo(v.mas_bottom).offset(tailSpacing);
                }];
            }
            prev = v;
        }
    }
}

这样的话,你可以给每个item不一样的宽高限制,这个方法只会给他们加spacing的限制,不会改变他们原有的宽高,并且这个方法还能用子view撑起这个view array的共同父view容器。
P.S. 其实有道算法题目就是找最近共同父view,思想和找链表最近节点是一样的,找到最上面然后算差,错开出发就可以了

  • 其实最开始代码不是这么写的,是给最后一个item加right contraint为父view right - tailSpacing,但是这样会有一个cell复用的bug哦~

假设上一个cell有两个item view A & B,即将出现的cell内有三个view A & B & C;之前的constraint加上的是viewB.right = superView.right - tailing,然后复用的时候viewB.right = viewC.left - spacing,这里viewB木有问题可以正常显示,但是viewC加了一个viewC.right = superView.right - tailing,于是superView现在有两个right的限定,viewB显示正常viewB.right = superView.right - tailing是生效的,那么由于viewC.right = superView.right - tailing也存在,算了一下以后viewC被挤到宽度为0了,于是就不显示了。

其实这个问题就是要复用的时候把superView的tailing约束清掉就可以了,如果是给最后一个item加约束,由于superView是被动加了约束,是不能用remake清掉别人给它的约束的,所以改成给superView加约束,然后cellForRowAtIndexPath的时候把superView除了tailing的约束remake一下,这样tailing就被清掉了于是就木有问题啦~


3. tableview中如何让行高自动被撑起来

可以参考:https://www.jianshu.com/p/5f5c550d61a0

懒杨杨大概说下步骤吧~

  • 设置tableView.rowHeight = UITableViewAutomaticDimension
    如此设置之后,就不必要写heightForRowAtIndexPath方法了。(默认值就是UITableViewAutomaticDimension)

  • 设置tableView.estimatedRowHeight = 100
    设置一个预估的行高,为了代码的易读性,还是尽量要设置一个跟cell的高差不多的值

  • 让内容自动撑开cell的contentView

// cell .m
#import "Table1ViewCell.h"
#import "Masonry.h"

@implementation Table1ViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentLabel = [[UILabel alloc] init];
        self.contentLabel.numberOfLines = 0;
        self.contentLabel.font = [UIFont systemFontOfSize:15.0];
        self.contentLabel.textColor = [UIColor blackColor];
        [self.contentView addSubview:self.contentLabel];
        [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.contentView).offset(10);
            make.bottom.equalTo(self.contentView).offset(-10);
            make.left.equalTo(self.contentView).offset(10);
            make.right.equalTo(self.contentView).offset(-10);
        }];
    }
    return self;
}

@end

// vc.m

#import "Table1ViewController.h"

#import "Table1ViewCell.h"

@interface Table1ViewController () 

@property (nonatomic, strong) NSArray *objects;

@property (weak, nonatomic) IBOutlet UITableView *tableview;

@end

@implementation Table1ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableview.dataSource = self;
    self.tableview.delegate = self;
    self.tableview.rowHeight = UITableViewAutomaticDimension;
    self.tableview.estimatedRowHeight = 100;
    
    self.objects = @[@"美商务部对中兴通讯暂时部分解禁,中兴新掌门李自学:尽快恢复生产",
    @"公告称:从公告发布之日起至8月1日,在有限条件下解除对中兴通讯公司的出口禁令。美国商务部公共事务总监丽贝卡·格洛弗在接受CGTN记者问询时表示,对中兴通讯的7年禁售令并未正式取消。",
    @"这份公告的授权对象是已经与中兴通讯开展业务的公司,期限是一个月。这些公司销售给中兴通讯的产品必须用于以下方面。第一是支持现有网络和设备的持续运行,其次是支持现有的手机,第三是用于网络安全研究和漏洞披露,另外还有一个条件是交易资金必须在美国商务部授权的机构间转移。",
    @"在6月29日上午举行的中兴通讯股东大会上,中兴新掌门人李自学发言时强调:“我们的任务还是提振整个公司的信心,包括公司的员工(信心),在拒绝令解除之后,尽快恢复生产,在这之后再做一些工作。”",
    @"美国芯片巨头美光部分产品在华遭禁售",
    @"7 月 3 日福州中级法院裁定对美国芯片巨头美光(Micron)发出“诉中禁令”,美国部分闪存 SSD 和内存条 DRAM 将暂时遭禁止在中国销售,虽不是最终判决,但似乎暗示美光确实有侵权之嫌。",
    @"美光方面周二表示,尚未收到竞争对手台联电早些时候提到的在中国大陆销售芯片的临时禁令。美光表示,在评估来自福州中级人民法院的文件之前,该公司不会对此置评",
    @"小米或成史上最快纳入恒生指数的港股,一手中签率100%",
    @"自小米宣布IPO以来,这家超级独角兽就在不停地创造纪录。",
    @"先是将成首家同股不同权的港股上市公司;招股书又披露在全球营收超千亿且盈利的互联网公司中,小米增速排名第一;认购结束后,小米录得近10倍超购,打破2011年以来大盘股认购倍数纪录,成为全球最大散户规模的IPO,是香港有史以来规模最大的民营公司IPO,全球第三的科技公司IPO",
    @"业内人士表示,小米上市后或将在10个交易日后被纳入恒生综合指数,创下港股史上最快纪录",
    @"此外,香港信报消息显示援引市场消息显示,小米一手中签率100%,逾2.5万人申请一手;至于获分派2手有1.5万人,认购9手稳获2手。至于B组的大户方面,最大一张飞为认购1000万股,获分97.2万股,即4860手。小米将于7月9日挂牌"];
}

#pragma mark - UITableViewDelegate
//- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//    return 60;
//}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.objects.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"Table1ViewCell";
    Table1ViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if (cell == nil) {
        cell = [[Table1ViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
    }
    
    cell.contentLabel.text = self.objects[indexPath.row];
    
    return cell;
}

以上代码跑出来是酱紫的~


不实现heightForRowAtIndexPath

如果你把注释调的heightForRowAtIndexPath放开就是酱紫的了:


实现heightForRowAtIndexPath

所以如果想自适应就不要实现heightForRowAtIndexPath哦~~

如果你有些cell想固定高度,有些想自适应就酱紫吧:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    BaseItem *item = [self.dataManager cellItemAtIndexPath:indexPath];
    if (item.cellHeight > 0) {
        return item.cellHeight;
    }
    return UITableViewAutomaticDimension;
}

4. scrollView缓慢滑动到边界松手不走scrollViewDidEndDecelerating

可参考:https://blog.csdn.net/abc649395594/article/details/46780065/

场景就是如果你缓慢滑动,一直滑到该停下的位置,那么其实你停下是被迫的,没有减速过程,不会调用scrollViewDidEndDecelerating,所以其实如果你想在停止滚动的时候做点什么,需要同时实现scrollViewDidEndDragging:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if(!decelerate){
        //这里复制scrollViewDidEndDecelerating里的代码
    }
}

5. UIButton的EdgeInsets来调整图片文字位置

  • 当button.width < image.width时,只显示被压缩后的图片,图片是按fillXY的方式压缩。

  • 当button.width > image.width,且 button.width < (image.width + text.width)时,图片正常显示,文本被压缩。

  • 当button.width > (image.width + text.width),两者并列默认居中显示,可通过button的属性contentHorizontalAlignment改变对齐方式。

// 下面实验的测试代码
- (void)testButtonEdge {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.layer.cornerRadius = 4;
    button.layer.borderWidth = 0.5;
    button.layer.borderColor = [UIColor blueColor].CGColor;
    
    [button setImage:[UIImage imageNamed:@"button_icon"] forState:UIControlStateNormal];
    
    button.titleLabel.font = [UIFont systemFontOfSize:20];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [button setTitle:@"HaHa" forState:UIControlStateNormal];
    
    button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
    button.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
    button.imageEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0);
    button.titleEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 10);
    [button sizeToFit];
    button.frame = CGRectMake(100, 100, button.frame.size.width, button.frame.size.height);
    button.frame = CGRectMake(100, 100, button.frame.size.width, 50);
    [self.view addSubview:button];
    
    UILabel *imageEdgeInsetsLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 160, 0, 0)];
    imageEdgeInsetsLabel.text = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %.0f)", button.titleEdgeInsets.top, button.titleEdgeInsets.left, button.titleEdgeInsets.bottom, button.titleEdgeInsets.right];
    [imageEdgeInsetsLabel sizeToFit];
    [self.view addSubview:imageEdgeInsetsLabel];
    
    UILabel *textFrameLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 180, 0, 0)];
    dispatch_async(dispatch_get_main_queue(), ^{
        textFrameLabel.text = [NSString stringWithFormat:@"(%.1f, %.1f, %.1f, %.1f)", button.titleLabel.frame.origin.x, button.titleLabel.frame.origin.y, button.titleLabel.frame.size.width, button.titleLabel.frame.size.height];
        [textFrameLabel sizeToFit];
        [self.view addSubview:textFrameLabel];
    });
}
(1)contentEdgeInsets

如果我们什么都不设置,一个正常的按钮如果设置了图片和文字,就和下图左上角的类似,button会紧紧包裹住文字和图片,并且title和image会紧挨着:

contentEdgeInsets

contentEdgeInsets可以设置文字+图片的外围距离button四周的inset,注意它是会改变button的大小的哦!

例如当我们设置inset为(20,0,20,0)的时候,可以看到相比inset设为0左右就多了一样的距离。

但是如果既设置了height,但上下inset加起来超过了height,就会变成左下角那种不知道怎么布局的布局了哦0.0

文字图片高不一致

当文字高度和图片不一致的时候,看起来是以最外围(高的一方)为准来设置inset的,如果设置button高度很高,本身文字和图片居中的时候距离上下的inset已经超过了设置,就会默认居中了,所以其实inset类似于最小边距吧,比它大就好~


(2)contentHorizontalAlignment & contentVerticalAlignment

从字面来看,其实这个属性就是水平和竖直方向如何对齐,它的取值如下:

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter        = 0,
    UIControlContentVerticalAlignmentTop           = 1,
    UIControlContentVerticalAlignmentBottom        = 2,
    UIControlContentVerticalAlignmentFill          = 3,
};

typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) {
    UIControlContentHorizontalAlignmentCenter = 0,
    UIControlContentHorizontalAlignmentLeft   = 1,
    UIControlContentHorizontalAlignmentRight  = 2,
    UIControlContentHorizontalAlignmentFill   = 3,
    UIControlContentHorizontalAlignmentLeading  API_AVAILABLE(ios(11.0), tvos(11.0)) = 4,
    UIControlContentHorizontalAlignmentTrailing API_AVAILABLE(ios(11.0), tvos(11.0)) = 5,
};

所以其实如果改变竖直方向的对齐方式,当设置height比内容高很多的时候,button就不一定内容居中了哦~

竖直对齐

(3)imageEdgeInsets

imageEdgeInsets调整image的上下左右边缘与content外围的距离(content外围和contentEdgeInsets也会有关系),该调整并不会改变UIButton原来的大小,image为了适应调整,可能会变形或者跑出UIButton的外面。

例如以上面的情形为例,当设置竖直方向是UIControlContentVerticalAlignmentTop,并且contentEdgeInsets = UIEdgeInsetsMake(5, 20, 5, 40),然后设置不同的imageEdgeInsets会是酱紫的:

imageEdgeInsets

还记得最开始那个height是50上下contentInset是5,并且竖直居中的例子么,上下inset看起来好像比5大很多,但是实际上就布局而言,除了上下5的部分都算是content了哦~

然后content内部才会去分imageEdgeInsets以及titleEdgeInsets。这也就是为什么当设置height为50,contentEdgeInsets为(5, 20, 5, 40),imageEdgeInsets设置(5, 0, 20, 0)或(5, 0, 0, 0)image的底部位置都是一样的。

当设置imageEdgeInsets为(5, 0, 30, 0)的时候,因为上面有contentEdgeInsets的5+ imageEdgeInsets的5=10,然后下面还有contentEdgeInsets的5+imageEdgeInsets的30=35,所以那个图标的top坐标是10,然后高度就是50-10-35=5。

inset示意图

需要注意的一点是,上下和左右是分别对应的,可以设置负值(例如第一个参数上,填正数代表向下移动),bottom和right都是如果是正的,就是想上/左移动一定距离,如果是负数,才是向下或右移动距离。如果设置了top/left inset,也应将bottom/right设置相同数字inset,但是要取负数哦。


那么right究竟是相对谁的呢?

我看了好多文章,有的说其实是相对button的,有些说是相对titleLabel的,这里我做了一些尝试来看一下~

仅设置 imageEdgeInsets.right

整体button是65的宽度,titleLabel大约50的宽度。可以看到当right设为10的时候,image根本没有移动,当right设为55以后,image的右侧才有了一些变化,所以其实当right是正数的时候,相对的是button右边缘的距离,因为本来就有大约50的距离,所以如果设为50以下的正数,image不会变化

如果设置的数字大于55,例如70或100,会发现image右边缘贴住了button的左边缘,image左边缘距离button右边缘的距离是我们写定的right inset。

如果把right设为负数,会发现如果设为-10,image右移了5;设为50,右移了25;设为150则右移了75。是否超过了button边距好像对计算没有影响,图片也不会拉伸。

如果right是正值,他就是相对于button右边缘的;如果超出了button宽度,image就出了左边缘了,这个值就是image左边缘到button右边缘的距离;如果是负值,则是和left值平均后,相对于原来的位置的右边缘做位移。


现在加上正left来看一下:
正left + right

当我们固定left inset为10,然后如果设置right为0到-10之间都没区别的;如果right的绝对值大于了-10并且和left正负颠倒,也就是向同一方向移动,例如设为-20,那么图片会右移(10 + 20)/2 = 15,图片尺寸不拉伸,同理right为-70的时候图片右移40。

如果right为正,在10到40之间对图片都没多大影响,因为本来图片右边缘距离button右边缘为(65按钮宽度 - 10图片左坐标 - 16图片宽度) = 39。如果设为45,图片左侧没有变但宽度压缩了,右侧距离button边缘为45;如果改为50就很有意思了,由于button宽度65,左右inset加起来等于65,图片就没有宽度啦,于是image不显示,仅显示title且居中;如果设为70或100,图片右侧维持在10坐标,也就是left inset的值,左侧距离button右边缘为70或100,符合之前的结论。


现在加上负left来看一下:
负left + right

当left设为-10,right设为0的时候,image左移了(10 + 0) / 2 = 5,如果right为0到10的区间,image左移的距离都是(right + left) / 2的距离,例如left为-10以及right为10的时候,image左移(10 + 10) / 2 = 10。

如果right>10的时候,例如20或者40,image和right设为10的时候没有区别,因为right距离button右侧距离为60;如果right设为65,那么现在的右侧距离已经不够了,image就会被挤压并且左侧仍旧在-10,右侧坐标为0;如果设为75,此时image的宽度被挤压为0不显示,title将居中显示;如果right设为150,图片右侧将变为left inset也就是-10,左侧距离button右侧为150。

当right设为-5的时候,图片左移了(10 - 5) / 2 = 2.5的距离;right设为-30的时候,图片左移了(10 - 30) / 2 =-10的距离,也就是右移了10距离;right设为-200的时候,图片左移了(10 - 200) / 2 =-95的距离,也就是右移了95距离。


这里总结一下关于imageEdgeInsets的right和left设置
  • 正right在max(-left,0)button右边缘到image右边缘距离之间的时候,image只听left inset布局即可,不用考虑右侧,图片不压缩。

  • 正right在button右边缘到image右边缘距离button右边缘到image左边缘距离之间的时候,图片左侧坐标按照left inset设置,右侧坐标距离button右边缘为right inset的距离,图片压缩。

  • 正right=button右边缘到image左边缘距离的时候,图片不显示,title居中显示。

  • 正right大于button右边缘到image左边缘距离的时候,图片右侧距离button左侧距离为left inset,图片左侧距离button右侧距离为right inset。

  • 负right在0min(0, -left)之间的时候,图片左侧根据left inset确定坐标,图片不压缩,右侧无变化。left为0不算在内哦。

  • 负right在min(0, -left)到负无穷之间的时候,图片不压缩,图片左侧坐标为(left inset - right inset) / 2。

  • left为负, 正right在0max(-left,0)之间,图片左侧坐标为(left inset - right inset) / 2。

其实以上只是为了搞清楚,实际应用很少这么干,如果向移动image位置,以左侧为准看要移动多少,然后right取反就行。

--

如果结合contentInset和imageInset嘞?
设置contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10)

可以看出,当存在contentEdgeInsets的时候,imageEdgeInsets所谓的button边缘是除去contentEdgeInsets的部分,也就是内圈,不是实际的button边缘哦。


(4)titleEdgeInsets
设置titleEdgeInsets的left

如果只设置titleEdgeInsets的left,如果是正值,则title向右移动left的数值,同时image会右移left/2的数值,所以当left设为50的时候,title就移没了,image会居中显示。

如果设为负数,那么title会左移left/2的数值,image不会动~

设置titleEdgeInsets的right(下面是titleLabel的frame)

讲真,只设置right的时候我也木有感受到有什么规律。。有的时候titleLabel的right距离button的right是设定的数值,有的时候不是;如果超过了button(button左侧坐标没有动,所以超过的标准就是right inset = 65 - 16 = 49),titleLabel就不显示了,image会居中。

如果right和left都设置的话:


right和left都设置

可以看到right和left是同一个方向(数值相反)设置,移动距离就是两者相加后平均;如果不是一个方向,就不一定怎么动了0.0,双正会比较奇怪,双负数就是两者抵消后平均。


应用
  • 如果你想正常的移动image或者title,千万要左右对称哦,上面的乱七八糟的结果都是由于左右不对称造成的,很难记具体的计算规则,但是只要两者对称其实就会正常移动你想要的距离的。

两个比较具体的应用:(1)让title在image前面;(2)让image在上,title在下,两者中心对齐。

我这里只做第二种啦:

- (void)testButtonEdge {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.layer.cornerRadius = 4;
    button.layer.borderWidth = 0.5;
    button.layer.borderColor = [UIColor blueColor].CGColor;
    
    [button setImage:[UIImage imageNamed:@"button_icon"] forState:UIControlStateNormal];
    
    button.titleLabel.font = [UIFont systemFontOfSize:20];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [button setTitle:@"HaHa" forState:UIControlStateNormal];
    
    button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
    [button sizeToFit];
    button.frame = CGRectMake(100, 100, button.frame.size.width, button.frame.size.height);
    [self.view addSubview:button];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        button.contentEdgeInsets = UIEdgeInsetsMake(button.imageView.frame.size.height / 2, 0, button.imageView.frame.size.height / 2, 0);
        button.imageEdgeInsets = UIEdgeInsetsMake(-button.imageView.frame.size.height, (button.frame.size.width - button.imageView.frame.size.width) / 2, button.imageView.frame.size.height, -(button.frame.size.width - button.imageView.frame.size.width) / 2);
        button.titleEdgeInsets = UIEdgeInsetsMake(button.imageView.frame.size.height, -button.imageView.frame.size.width / 2, -button.imageView.frame.size.height, button.imageView.frame.size.width / 2);
        [button sizeToFit];
    });
}
图片在上文字在下

P.S. 讲真button这几个inset真的是玄学0.0~ 最好的方式。。就是尝试。。。但contentEdgeInset还是可以算的毕竟只有它可以改变size

以及注意哦,我们即使改变了各种inset,button的frame.origin是没有变的。。


6. UITableViewCell的子view如果超过了cell的边界,会导致点击穿透

这个场景是,我希望在一个cell上面加一个气泡(气泡位置和cell内的button有关),但是气泡已经出了自身cell的边界了,点击气泡是要消失的。

当我把气泡加了点击手势以后发现点击会触发它overlap的cell的点击事件,而不会触发气泡的点击手势,原因就是气泡已经出了cell的可以控制的事件范围了。

所以最后的做法是气泡需要加到table上面,由cell提供接口,让table可以获取到应该加到什么位置(cell内button位置),然后在cell即将display的时候调用show即可。

可以通过类型转换来判断cell,也可以通过confirm protocol来提供接口,但最好不要用responseTo的runtime调用,比较容易出错哦~

需要注意的是,数据源更新的时候最好让气泡消失,要不也挺麻烦的要重新找到cell,以及这个cell现在的位置,然后再移动气泡。


7. sizeToFit和sizeThatFits

可以参考:https://www.jianshu.com/p/b10b367bd72a

总体而言就是:
sizeThatFits: 计算出最优的size并返回,不会改变当前view的size(仅计算)
sizeToFit: 会调用sizeThatFits,根据返回的size修改自己的size(计算+改变view)

懒得写代码了就借别人家的代码了0.0

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 0, 0)];
[label setFont:[UIFont systemFontOfSize:20]];
label.text = @"北京欢迎您!!!";
[label sizeToFit];

NSLog(@"width=%.1f  height=%.1f ", label.frame.size.width, label.frame.size.height);
[self.view addSubview:label];

输出:width=163.5 height=24.0

// 如果用下面的替换sizeToFit
CGSize sizeThatFits = [label sizeThatFits:CGSizeZero];
NSLog(@"sizeThatFits: width=%.1f  height=%.1f", sizeThatFits.width, sizeThatFits.height);
NSLog(@"width=%.1f  height=%.1f", label.frame.size.width, label.frame.size.height);

输出:
sizeThatFits: width=163.5  height=24.0
width=0.0  height=0.0
  • sizeThatFits的参数有啥用呢?

传说是酱紫的:
当使用CGSizeZero时,其返回的size为正常显示的size;当你自定义某个size时,例如:CGSizeMake(80, 80),当计算的最合适的size比自定义的数值小时,返回正常计算的size,当计算的最合适的size比自定义的数值大时,这时返回的将是在自定义size范围内的最合适的size。

但对label而言无论我怎么改传入的size好像返回的都一样0.0,button我也试了一下也是没能实现用参数size来limit,可能是有些view有这么做有些木有吧-。-

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 0, 0)];
label.text = @"北京欢迎您!!!";

CGSize sizeThatFits = [label sizeThatFits:CGSizeMake(10, 500)];
NSLog(@"sizeThatFits: width=%.1f  height=%.1f", sizeThatFits.width, sizeThatFits.height);

8. masonry和frame不要混用

如果用masonry布局,然后用frame做动画(UIView animate)有可能会产生奇奇怪怪的现象,所以尽量不要混用叭。

你可能感兴趣的:([iOS] UI控件及布局的小知识们(1))