关于如何写UI及屏幕适配的一些技巧(下)

接上篇:关于如何写UI及屏幕适配的一些技巧(上)


上篇发出之后收集了一些反馈, 总结起来以下几点:

  1. 没有demo,代码没有全部粘上来。
    我认为这篇的内容不需要,不能我写个弱引用的懒加载怎么写,还一定要把声明weak属性的代码放上来吧。
  2. 关于xib/strotyboard和纯代码
    我是两种用过,实际工作中也基本都达到了熟练的程度,现在使用纯代码也不是公司要求,自己觉得更好,其他的就不说了,这就相当于两条路都行的通,任意一条走成老司机了另一条也没问题,但是新人工作中最好纯代码为主。
  3. 关于懒加载是否一定要
    理解到位想怎么搞怎么搞,萝卜青菜各有所爱。

提纲:上篇说到第4条
1. 关于xib/storyboard 与 纯代码的对比  
2. 一条规范(又提了一点关于命名的)
3. UI工厂类 与 代码块
4. 懒加载, View使用strong还是weak
5. 复杂界面要会分区,要会障眼法
6. masonry均布View,及其布局时约束依赖关系
7. 关于屏幕适配的一点技巧
8. 循环引用(上篇文章有人对循环引用不理解,虽然是基础,有人不理解还是说一下吧)
5. 复杂界面要会分区,要会用障眼法

分区:什么叫分区,其实就是封装,干啥其实都是一样,UI网络逻辑思路有相同的地方,就包括收拾东西,为啥很多人喜欢把各种东西用各种盒子装起来,假设现在要写一个aView,上面是这样的

一个View上的元素

这个要怎么写, 直接挨个创建直接往aView上加吗, 这以后维护起来改点东西相信你死的心都会有的,一般这种元素有点多的都要适当的分一下区

分区后

这样分区后,如图所示,写UI的时候就先依次单独解决好上中下三部分,然后需要做的就是对上中下三部分的整体进行布局,这一级布局的时候就完全可以忽略他们内部的东西是什么样的,全部完成整体微调,该调里面就里面, 该调整体就整体

障眼法: 所谓障眼法就是投机取巧,当然可以有各种各样的方法,把一些复杂功能简化,不管用了什么方法,最终看起来像是实现了就可以。
下面举个例子, 这个例子是项目中的一个界面, 我简化了一下抽出来, 这是一个消费记录的界面, 有个tableview,每个cell如下所示,可以展开收起

展开状态
收起状态

看到这样一个界面,首先不要考虑如何展开收起,就看一下展开的要怎么写,(演示Demo中的UI因为没有使用网络数据,也为了演示方便,做了简化,实际账单消费下面还有一部分如何消费,可获得什么等等的区域),参照上一条,这种一个View里元素较多的时候可以先分区如下:

按功能或者位置分区如图

先假设展开状态已经写好了,下面要考虑如何收起,观察UI发现收起状态的信息是展开状态中的主要信息, 如图元素其实表达的是同样信息

Paste_Image.png

那么难道要打破布局,将这几个view找到重新布局,其他的隐藏掉吗?那再点击回到展开状态怎么办,在重新布局?想想就麻烦

所以,这时再搞一个summaryView,负责收起的信息展示,这个View内部的时间桌号等控件,跟展开状态的时间桌号虽然长的一样,但是实际是两个不同的UI对象。

summaryView

所以完成之后,这个View里会有如下几大块

  • summaryView (收起的View)
  • expendBgView(展开时的整体View)
    • topView (这样命名不好)
    • midView

如此布局,在点击了View要展开/收起的时候,只需要转换summaryView和expendBgView的隐藏状态,改变一下最外层View的底部约束即可

demo地址:https://github.com/CoderLXWang/LayoutViewDemo

6. masonry均布View,及其布局时约束依赖关系

均布View: 等间距布局 - 从0开始说一下masonry的使用

约束依赖关系:这个标题其实比较宽泛,也说不好,如何写约束本身就是比较灵活的,每个人的写法可能都不一样,下面举两个例子大概说一下,
示例1:

示例1

这个很简单, 左右间距都是30,第二三四行View的左右约束该怎么写,都写下面的吗?这样写如果要改这个30,就瞎了

make.left.equalTo(父视图).offset(30);
make.right.equalTo(父视图).offset(-30);

因为这里的设计就是左右都要对其,所以下面都都依靠第一个布局即可,第二行两个不是左右都对其

make.left.right.equalTo(父视图).offset(第一个View);

代码少了一行是其次,主要是改的话只改一个,也可以透过代码看到这个地方的设计
注:这个示例很简单,勿喷,主要说这种做法,复杂布局也需要考虑到底依靠那个View布局,具体体况多体会,简单说就是要选取合适的依赖对象

示例2:
需求:
1.整体居中
2.宽度可变,看文字是否够一行,最宽左右内边距10
3.内部两个View的centerY对其
4.最小高度为图片高度,文字高度超度图片,就以文字高度为准

直接上代码,只为说明约束,不要找别的毛病,具体自己看吧,这里Label和ImageView一定要作为一个整体(即放到同一个父视图中),内部因为图片相对固定,左右尺寸都不变,要先布局图片才可以,否则Label没有可以依赖的东西

@interface ViewController ()

@property (nonatomic, strong) UIView *containerView;

@end

@implementation ViewController

- (UIView *)containerView {
    if (!_containerView) {
        _containerView = [[UIView alloc] init];
        _containerView.backgroundColor = [UIColor orangeColor];
       
        UIImageView *imgView = [[UIImageView alloc] init];
        imgView.image = [UIImage imageNamed:@"demo1.jpeg"];
        [_containerView addSubview:imgView];
        [imgView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.right.equalTo(_containerView).offset(0);
            make.size.mas_equalTo(CGSizeMake(100, 100));
            make.centerY.equalTo(_containerView);
        }];
       
        UILabel *label = [[UILabel alloc] init];
        label.numberOfLines = 0;
        label.text = @"这是阿三冲击红进口付出dsk红进口付出ds口付出dsk红进口付出dskjfhks口付kjfhks口付出dskj红进口付出dskjfhks口付出dskjjfhks口付出dskjfhd付出dsk红进口付出ds口付出dsk红进口付出dskjfhks口付kjfhks口付出dskj红进口付出dskjfhks口付出dskjjfhks口付出dskjfhdjfhdksjhfdk";
        [_containerView addSubview:label];
        [label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.right.equalTo(imgView.mas_left).offset(-20);
            make.left.equalTo(_containerView).offset(0);
            make.bottom.top.equalTo(_containerView).offset(0);
            make.height.mas_greaterThanOrEqualTo(100);
        }];
    }
    return _containerView;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.containerView];
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view).offset(200);
        make.centerX.equalTo(self.view);
        make.width.mas_lessThanOrEqualTo([UIScreen mainScreen].bounds.size.width-20);
    }];
}
7. 关于屏幕适配的一点技巧

首先说一个屏幕适配到底是什么, 工作中很多人,甚至产品都搞错了
所谓屏幕适配,并不是大屏就要将UI变大,而是要显示更多的内容。
再说一个关于按钮的写UI原则
按钮设计的大没啥可说,如果设计的按钮很小,到程序员手里一定要让它看起来小,点起来大
KRATE :当然在这一基本原则下,有的时候大屏上的某些元素和小屏保持同样大小会有一些难看,这时还是要分别对待,如果以5s屏幕尺寸为基准(也有用6的尺寸做基准的,都一样,习惯问题),这里一般会定义这样一个宏

#define KRATE (SCREEN_WIDTH/320.0)

举个例子


5s效果

不做比例的适配,在6p上如图

6p效果

其实也没啥问题,看起来也没有很不协调的地方,但是注意看一下券左右两条竖直虚线,会发现大屏上左面券命和右面打印的宽度会比较小,中间区域显得过大,应该稍微匀一点给左右两边,两边看起来会不那么挤,同时右侧点击范围也会相应放大,做法就是将左右约束的值*KRATE

[self.leftLine mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.bgView).offset(80*KRATE);
        make.top.equalTo(self.bgView).offset(15);
        make.bottom.equalTo(self.bgView).offset(-15);
        make.width.mas_equalTo(1);
}];
    
[self.rightLine mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.bgView).offset(-40*KRATE);
        make.top.bottom.equalTo(self.bgView).offset(0);
        make.width.mas_equalTo(1);
}];

乘上比例之后,效果如图

*KRATE后的6P效果

为什么会有这种问题产生, 其实也是由于个人的布局习惯引起的,在布局这个UI时,我是先将左右两条虚线定位好,内部的东西根据虚线的位置确定,而虚线的位置就是一块白色背景左右给定值布局的,所以换到大屏会左右宽度不变,这种情况乘个KRATE就可以了。
麻烦有些人不要来喷啥你写就不会有这个问题,按比例分区怎样的,那是你的方法,你要是按比例分区不也要想到底给0.几的比例吗,要是竖直方向在scollView里也有这个问题呢

KKRATE:因为KRATE是用不同屏幕的宽度算出的一个系数,假设某个宽度5s上宽度为10,KRATE后6上则为10375/320=11.7, 也就是屏幕大1号之后原本为10的宽度增大了1.7, 那么这个宽度如果是40呢,40*374/320 = 46.9。
为了解决小宽度 ×KRATE基本没效果或者宽度大 ×KRATE 又过分了的问题,又定义了这样一个宏,给KRATE在乘一个自己制定的系数,感觉没效果就给KKRATE传个大于1的系数,感觉过分了就KKRATE(0.95),这里注意传入的系数小于0.86就反而大屏UI更小了,这里不想在里面继续写个三目运算符判断了,就这样了

/** 在屏幕比例基础上再次比例, 大于0.86, 否则反而变小 */
#define KKRATE(rate) (KRATE > 1 ? KRATE*rate : KRATE)

拿一个界面举个例子,如图

5s效果

这里左右看起来窄窄的间距用的都是6dp,6dp如果直接*KRATE基本没用,乘完也就加一个dp,效果基本就是如下,屏幕很大,间距很小气,也许你会说小屏上也小气,设计说了,你不懂,正好

6P效果

如果将各处左右间距设置为

make.left.equalTo(ws.view).offset(6*KKRATE(1.8));
make.right.equalTo(ws.view).offset(-6*KKRATE(1.8));

效果如下

*KKRATE后6P效果

明显大气了许多。。。

8. 循环引用(上篇文章有人对循环引用不理解,虽然是基础,有人不理解还是说一下吧)

这部分是计划外的,因为上篇有不少同学问起这个东西,发现不少人对看似简单的循环引用概念还是比较模糊,所以我就拿出来说一下,我会分别解释一下常见的循环引用,以及代理,block中的循环引用问题,这里只做理解解释,没有深入研究,大神直接略过吧。
先说一下内存管理,大家都知道内存管理在MRC下要手动写retain,release等代码,操作一个对象的引用计数,以此控制对象持有及释放,ARC下编译器会自动添加retain/release等代码,ARC的一个基本规则就是,只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁。
所以当前我们的代码基本都是ARC,当我们研究一个对象是否循环引用时,也就不考去考虑计数到底为几,什么时候retain,什么时候release,我们只需要按照ARC的基本原则关心指向这个对象的strong指针。
下面就按这个基本原则解释一下循环引用,观察是否释放在控制器打印dealloc方法即可
示例1:简单粗暴无逻辑演示
有一个控制器SampleRetainCycleController *retainVCretainVC.view上面有个SampleRetainCycleView *testViewtestView有个强引用指针,指向retainVC,看起来貌似循环成一个圈了,这就是循环引用吗?貌似怪怪的,因为少了一个引用

简单的循环引用?

实际上少了一个引用关系,没有考虑retainVC是那里来的,retainVC被创建之后加载nav导航栈里是被navController强引用的,这是我们就可以按ARC的原则分析了,就是看线,找实线,这里我们关心的是控制器会不会被正常释放,那我们就看控制器有几根实线,这时就会发现有两根,pop出去的时候,上面的那条nav的线断了,但是还有一条View的线,所以控制器就不会被释放


实际循环引用图示1

示例2:代理为什么用weak声明
如图就是代理为什么用弱引用,如果用强引用就变成示例1的情况了


代理用弱引用原因

示例3:一般使用block为什么注意循环引用,使用weakSelf
先说为什么block一定要用copy,既然会循环引用,那么就像代理一样,使用弱引用的指针不行吗?
详细看这篇文章吧 Block为什么使用copy修饰
更详细可以看这篇谈Objective-C block的实现
总结起来就是为了使其存放在堆中,如果不copy一下,block是存放在栈中的,出了创建它的作用域,就可能被释放掉,但是用了copy,对这个block就是强引用,所以需要注意循环引用,使用weakSelf。
那什么是weakSelf,block默认对内部引用的外部变量是强引用,所以如果直接使用了self,则相当于block有一条实线(强指针)指向self,则self又有两条实线了

block为什么需要weakSelf

示例4:什么样的block不会造成循环引用
最常见的就是系统的一些block与masonry,系统的比如:

    [UIView animateWithDuration: animations:^{
        
    }];

首先是self(假设是当前的控制器)并没有copy(强引用)这个block,其次这还是个类方法,类方法里不能对属性进行复制,即也不能强引用这个block,所以直接用self(即block对self强引用)也不会形成循环引用

再比如masonry

[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {

}];

看一下mas_makeConstraints是怎么写的

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

只有第三行执行了一下这个block,并没有任何引用的代码(即类似self.xxblock = block),所以根据上面的几篇文章,这种block是存放在栈上的,出了作用域(即这个方法)就会被释放掉,既然block都被释放掉了,自然不会循环引用。

demo地址:https://github.com/CoderLXWang/RetainCycleDemo

上篇地址:关于如何写UI及屏幕适配的一些技巧(上)


码字不易,共同进步,欢迎提意见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容