美文网首页OC开发iOSiOS Development
iOS利用layoutSubviews, drawRect效率方

iOS利用layoutSubviews, drawRect效率方

作者: Kobe_Dai | 来源:发表于2017-02-22 22:43 被阅读820次

    在app的开发过程当中,我们会根据业务需求,封装一些UI的控件,在写控件的过程中,我们经常会遇到这样的问题,在什么方法里导入控件需要的数据,在什么方法里对数据进行计算,在什么方法里绘制控件,那么这篇文章就讲一下我对一个UI控件构建的流程和理解

    一个UI控件的构成总共分为4步:

    • 导入数据,初始化控件
    • 根据控件绘制的需求,对数据进行计算
    • 绘制控件
    • 提供一个方法用来根据新的数据刷新控件

    下面我用一个常见的可横滑切换的SegmentedControl这个UI控件来解释这四个步骤应该怎么实现,效果图如下:

    WechatIMG62.jpeg

    首先我们创建这个控件,叫SegmentedControl,基于UIControl,这个控件由一个UIScrollView跟多个CATextLayer组成

    1. 导入数据,初始化控件

    利用init方法创建控件,并导入控件所需的数据

    - (id)initWithConfig:(SegmentedControlConfig *)config
    {
        if (self = [super init]) {
            self.config = config;
            
            [self initSegmentControl];
        }
    }
    
    - (void)initSegmentControl
    {
        // 初始化该控件所需的properties
        // 初始化UIScrollView
    }
    

    2. 根据控件绘制的需求,对数据进行计算

    在一个控件里,我们会用layoutSubviews来进行数据计算,layoutSubviews将会在设置控件frame(非CGRectZero) 的时候被调用。官方给出的layoutSubviews解释是Subclasses can override this method as needed to perform more precise layout of their subviews,由于所有的CATextLayer的segments跟UIScrollView都是该控件的subviews,所以我们使用layoutSubviews来根据提供的数据计算它们的layouts

    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        [self updateSegmentsLayout];
    }
    
    - (void)updateSegmentsLayout
    {
        // 根据self.config计算所有的CATextLayer segment的宽度跟高度,并用两个array保存以便绘制时使用
        // 根据总宽度计算UIScrollView的frame跟contentSize
    }
    

    补充说明下layoutSubviews的调用机制,在这几种情况下会被调用:

    • 初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame的时候就会触发
    • addSubview会触发,前提是frame不为CGRectZero
    • 设置viewframe会触发,前提是frame不为CGRectZero
    • 滚动一个UIScrollView会触发
    • 旋转screen会触发superview上的layoutSubviews
    • setNeedsLayout手动触发layoutSubviews
    • 改变一个UIView大小的时候也会触发superview上的layoutSubviews
    • removeFromSuperview 只会调用superviewlayoutSubviews方法

    3. 绘制控件

    在一个控件里,我们会用drawRect:来绘制控件里所有的views, 控件在第一次displayed的时候会调用drawRect

    - (void)drawRect:(CGRect)rect
    {
        // 根据updateSegmentsLayout方法里计算得到的segments宽度高度的数据,来绘制全部,上海,北京 etc. 的CATextLayer segments
    }
    

    补充说明下drawRect的调用机制,在这几种情况下会被调用:

    • 如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadViewController->viewDidLoad两方法之后掉用的。所以不用担心在控制器中,这些ViewdrawRect就开始画了。这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).
    • 该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
    • 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:
    • 直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
    • view第一次displayed会触发drawRect:

    4. 根据新的数据刷新控件

    由于我们用layoutSubviewsdrawRect两个方法来计算,绘制控件,那么用新数据来刷新控件的方法会非常的简单并清晰,我们只需在获取新数据之后手动调用layoutSubviewsdrawRect这两个方法即可

    - (void)reload:(SegmentedConfig *)config
    {
        self.config = config;
        
        [self setNeedsLayout];    // call layoutSubviews
        [self setNeedsDisplay];   // call drawRect
    }
    

    5. 在业务层创建这个控件

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        [self.view addSubview: self.segmentedControl];
    }
    
    - (SegmentControl *)segmentedControl
    {
        if (!_segmentedControl) {
            _segmentedControl = [[SegmentedControl alloc] initWithConfig:self.config];
            _segmentedControl.frame = CGRectMake(0, 0, self.view.frame.size.width, 60); // will call layoutSubviews
        }
        return _segmentedControl;
    }
    

    控件完整代码如下:

    - (id)initWithConfig:(SegmentedControlConfig *)config
    {
        if (self = [super init]) {
            self.config = config;
            
            [self initSegmentControl];
        }
    }
    
    - (void)initSegmentControl
    {
        // 初始化该控件所需的properties
        // 初始化UIScrollView
    }
    
    - (void)updateSegmentsLayout
    {
        // 根据self.config计算所有的CATextLayer segment的宽度跟高度,并用两个array保存以便绘制时使用
        // 根据总宽度计算UIScrollView的frame跟contentSize
    }
    
    - (void)reload:(SegmentedConfig *)config
    {
        self.config = config;
        
        [self setNeedsLayout];    // call layoutSubviews
        [self setNeedsDisplay];   // call drawRect
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        [self updateSegmentsLayout];
    }
    
    - (void)drawRect:(CGRect)rect
    {
        // 根据updateSegmentsLayout方法里计算得到的segments宽度高度的数据,来绘制全部,上海,北京 etc. 的CATextLayer segments
    }
    
    

    总结下,创建一个控件的正确步骤是这样的:init -> layoutSubviews -> drawRect:

    跟SegmentedControl这个控件配套的是下面可以横滑的PagerViewController的一整套控件,跟网易新闻的类似,近期会整理出来会放GitHub上

    转载请注明出处,原文地址:http://kobedai.me/p9rsts-6h/

    相关文章

      网友评论

        本文标题:iOS利用layoutSubviews, drawRect效率方

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