Frame布局入门看我就够了

初识

在IOS中最常用的两种布局方式是Frame布局和AutoLayout布局。本文主要讲解Frame布局的相关知识。
Frame布局使用简单,适合初学者,可以采用手写或者xib进行布局,适用于比较简单的界面布局,复杂的布局如果使用Frame布局逻辑会很复杂,不利于布局和后期维护。
Frame的数据结构简单,类型为CGRect,其中original属性定义point,size属性定义大小,初始化成功一个Frame布局之后,可以使用setFrame修改布局。frame的值影响自己的布局,bounds只影响其子view的布局。

UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[self.view addSubview:view];

适用场景

有时候会遇到一些稍微复杂度一点的界面布局,这时候可以使用函数或者对象封装原生的frame布局方法,好的封装让我们可以使用链式布局[^1]的方法,和更简单的方式进行frame布局。
已经有前人实现了frame布局的相对布局[5]
也有三方库支持像使用masory一样使用frame布局[6]和[7]
但是很复杂的界面就不建议使用frame布局了,可以使用autolayout布局,当然还有其它布局方式,这儿不讨论。
Frame布局的效率比AutoLayout高得多,下面是他们的效率比较图表。


performance.png

图片来源和性能分析更详细可参考[2]

调试

可以使用系统自带的debug view hierachy和第三方的Reveal查看当前界面的布局情况。

Debug view hierachy:


debug1.png

Reveal:


debug2.png

可以在lldb中使用po查看view的frame值,这也是我们调试界面布局的一个重要参考指标,如果不能查看,需要手动在lldb中运行e @import UIKit,然后po就可以了。(如果不清楚po是什么,需要先去看下lldb的基础知识)

Frame动画

可以通过改变frame布局的值来实现一些简单的动画,当然这儿不使用frame是因为
一般外部使用view,使用frame;内部使用或者定义view,使用bounds;动画和旋转一般基于center来进行,很少通过操作frame。

[UIView animateWithDuration:0.5 animations:^{
                
       self.view.center = CGPointMake(20, 20);
                
       } completion:^(BOOL finished) {
                    
                    //do something       
 }];

Frame的本质

每个view都有一个frame,这个frame实际是layer的frame,view只是透传frame的值。修改view的frame布局,其实修改的是layer的frame布局。
Frame属性只是一个计算属性,它最后的值由bounds,anchor,position和transform这几个实际属性计算获得,他们之间会相互影响。具体的算法如下,参考文章[3]和参考[4]。
下面是frame计算的伪代码


 -(CGRect)frame
{
      CGRect retValue = CGRectZero;
      if (CGAffineTransformIsIdentity(self.transform))
      { //没有设置仿射变换的情况下
           
            //位置等于中心点的位置减去视图尺寸乘以锚点的值。
            retValue.origin.x = self.center.x - self.bounds.size.width * self.layer.anchorPoint.x;
            retValue.origin.y = self.center.y - self.bounds.size.height * self.layer.anchorPoint.y;
           //尺寸等于视图的尺寸
            retValue.size.width = self.bounds.size.width;
            retValue.size.height = self.bounds.size.height;
      }
      else
      {
            CGAffineTransform left =  CGAffineTransformMakeTranslation(-1 * self.bounds.size.width * self.layer.anchorPoint.x, -1 * self.bounds.size.height * self.layer.anchorPoint.y);
            //因为下面的坐标变换应用是从(0,0)开始的,因此这里的right指定中心点的位置,也就是下面的复合变换右乘right来实现位置的变换处理。
            CGAffineTransform right =  CGAffineTransformMakeTranslation(self.center.x, self.center.y);
            //整个复合变换是left 左乘 视图的tansform属性然后再右乘right变换。
            CGAffineTransform concat  = CGAffineTransformConcat(CGAffineTransformConcat(left, self.transform), right); 
            retValue  = CGRectApplyAffineTransform(CGRectMake(0,0,self.bounds.size.width, self.bounds.size.height), concat);
      }

    return retValue
}

立即刷新Frame

frame布局可以使用setneedlayout设置刷新布局标志位,如果需要立即刷新调用layoutifneed方法,会立即触发调用layoutsubviews,你可以在这个接口中做很多事,比如你可以在这个接口里面刷新它父亲的布局。

@implementation ViewController

-(void)viewDidLayoutSubviews
{
    //Second
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    UIView* test = [[UIView alloc] initWithFrame:CGRectMake(0, 44, 20, 20)];
    test.backgroundColor = [UIColor blackColor];
    
    [self.view addSubview:test];//First
    
    [self.view layoutIfNeeded];
    
    NSLog(@"3");    //Third
}


@end

从上述代码中的First,Second和Third可以看出执行顺序。

Autoresizingmask

使用Autoresizingmask,系统会自动生成frame布局,并且这个布局在父view变化的时候会跟随变化。

适配

frame布局在处理适配的问题上比autolayout需要做更多工作。frame布局在适配不同的机型上可能会出现很多的魔法变量,当然可以用宏定义解决一部分问题,但是会使得后期难以维护和理解,毕竟代码大部分时间都是用来阅读的,所以可读性还是很重要的。

[1] 链式布局:https://www.jianshu.com/p/67fd5332e7b9

[2] 性能分析:https://draveness.me/layout-performance

[3] Frame计算:https://www.jianshu.com/p/a12cc7356c99

[4] Frame计算:https://www.jianshu.com/p/00482643234d

[5] Frame相对布局:https://github.com/huisedediao/UIView-FrameLayout

[6] Frame相对布局:https://www.jianshu.com/p/d99e94e6b9f9

[7] Frame相对布局:https://www.jianshu.com/p/b76947766583

你可能感兴趣的:(Frame布局入门看我就够了)