知识点31:懒加载子控制器及其view

1.主控制器中

  • 父子控制器: 在scrollview中懒加载子控制器(的view)
#import "XMGEssenceViewController.h"
#import "XMGTitleButton.h"
#import "XMGAllViewController.h"
#import "XMGVideoViewController.h"
#import "XMGVoiceViewController.h"
#import "XMGPictureViewController.h"
#import "XMGWordViewController.h"

@interface XMGEssenceViewController () <UIScrollViewDelegate>
/** 当前选中的标题按钮 */
@property (nonatomic, weak) XMGTitleButton *selectedTitleButton;
/** 标题按钮底部的指示器 */
@property (nonatomic, weak) UIView *indicatorView;
/** UIScrollView */
@property (nonatomic, weak) UIScrollView *scrollView;
/** 标题栏 */
@property (nonatomic, weak) UIView *titlesView;
@end

@implementation XMGEssenceViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 0.设置导航控制器
    [self setupNav];
   
    // 1.添加子控制器
    [self setupChildViewControllers];
    
    // 1.设置scrollview
    [self setupScrollView];
    
    // 2.设置标题栏
    [self setupTitlesView];
    
    // 默认添加子控制器的view
    [self addChildVcView];
}


// 注意1: 设置父子控制器(方便设置子控制的view布局到父控制器的view上,方便获取子控制的控件)
// 添加的子控制器都是tableViewController,view都是tableview
- (void)setupChildViewControllers
{
    XMGAllViewController *all = [[XMGAllViewController alloc] init];
    [self addChildViewController:all];
    
    XMGVideoViewController *video = [[XMGVideoViewController alloc] init];
    [self addChildViewController:video];
    
    XMGVoiceViewController *voice = [[XMGVoiceViewController alloc] init];
    [self addChildViewController:voice];
    
    XMGPictureViewController *picture = [[XMGPictureViewController alloc] init];
    [self addChildViewController:picture];
    
    XMGWordViewController *word = [[XMGWordViewController alloc] init];
    [self addChildViewController:word];
}

- (void)setupScrollView
{
    // 注意2: 不允许自动调整scrollView的内边距(是控制器的方法),让scrollview后续能覆盖全屏
    self.automaticallyAdjustsScrollViewInsets = NO;
    
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.backgroundColor = XMGRandomColor;
    scrollView.frame = self.view.bounds;
    scrollView.pagingEnabled = YES;
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.delegate = self;
    // 注意3: 添加所有子控制器的view到scrollView中(父子控制器的运用,获取子控制器的个数)
    scrollView.contentSize = CGSizeMake(self.childViewControllers.count * scrollView.xmg_width, 0);
    [self.view addSubview:scrollView];
    self.scrollView = scrollView;
}

- (void)setupTitlesView
{
    // 标题栏
    UIView *titlesView = [[UIView alloc] init];
    // 注意4: 如果你想让标题的view有穿透效果,则可以设置alpha属性,但是如果你想字体不透明,则要设置背景颜色的alpha属性
    titlesView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
    titlesView.frame = CGRectMake(0, 64, self.view.xmg_width, 35);
    [self.view addSubview:titlesView];
    self.titlesView = titlesView;
    
    // 添加标题
    // 注意5: 将标题内容用数组保存
    NSArray *titles = @[@"全部", @"视频", @"声音", @"图片", @"段子"];
    NSUInteger count = titles.count;
    CGFloat titleButtonW = titlesView.xmg_width / count;
    CGFloat titleButtonH = titlesView.xmg_height;
    for (NSUInteger i = 0; i < count; i++) {
        // 创建
        XMGTitleButton *titleButton = [XMGTitleButton buttonWithType:UIButtonTypeCustom];
        // 注意6: 绑定tag方便后续使用
        titleButton.tag = i;
        // 为按钮添加监听方法
        [titleButton addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];
        [titlesView addSubview:titleButton];
        
        // 设置数据(从数组中取出数据)
        [titleButton setTitle:titles[i] forState:UIControlStateNormal];
        
        // 设置frame
        titleButton.frame = CGRectMake(i * titleButtonW, 0, titleButtonW, titleButtonH);
    }
    
    // 注意7: 按钮的选中颜色(从父控件中拿到第一个子控件)
    XMGTitleButton *firstTitleButton = titlesView.subviews.firstObject;
    
    // 底部的指示器
    UIView *indicatorView = [[UIView alloc] init];
    // 注意8: 拿到按钮选择状态下,文字的颜色
    indicatorView.backgroundColor = [firstTitleButton titleColorForState:UIControlStateSelected];
    indicatorView.xmg_height = 1;
    indicatorView.xmg_y = titlesView.xmg_height - indicatorView.xmg_height;
    [titlesView addSubview:indicatorView];
    self.indicatorView = indicatorView;
    
    // 注意9: 立刻根据文字内容计算label的宽度(在viewDidLoad方法中执行这些代码,可能indicatorView尚未计算好firstTitleButton.titleLabel的宽度,可以使用sizeToFit,强制计算titleLabel的宽度)
    [firstTitleButton.titleLabel sizeToFit];
    // 设置第一个默认的按钮指示器
    indicatorView.xmg_width = firstTitleButton.titleLabel.xmg_width;
    indicatorView.xmg_centerX = firstTitleButton.xmg_centerX;
    
    // 注意10: 默认选中情况 : 选中最前面的标题按钮
    firstTitleButton.selected = YES;
    // 记录第一个选中的按钮,即默认选中按钮
    self.selectedTitleButton = firstTitleButton;
}

- (void)setupNav
{
    self.view.backgroundColor = XMGCommonBgColor;
    // 标题
    self.navigationItem.titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MainTitle"]];
    // 左边
    self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithImage:@"MainTagSubIcon" highImage:@"MainTagSubIconClick" target:self action:@selector(tagClick)];
}

#pragma mark - 监听点击
- (void)titleClick:(XMGTitleButton *)titleButton
{
    // 1.控制按钮状态
    // 移除上个按钮选中的状态->让点击按钮的状态为选中状态->记录当前选中的按钮
    self.selectedTitleButton.selected = NO;
    titleButton.selected = YES;
    self.selectedTitleButton = titleButton;
    
    // 2.选中指示器
    // 注意8: 指示器(重新设置指示器的宽度和中心点x值)
    [UIView animateWithDuration:0.25 animations:^{
        self.indicatorView.xmg_width = titleButton.titleLabel.xmg_width;
        self.indicatorView.xmg_centerX = titleButton.xmg_centerX;
    }];
    
    // 3.让UIScrollView滚动到对应位置
    // 注意11: 取出scrollView的偏移量,只改变x的值
    CGPoint offset = self.scrollView.contentOffset;
    offset.x = titleButton.tag * self.scrollView.xmg_width;
    // 注意12: 只有通过scrollview的setContentOffset方法才能调用scrollview的代理方法scrollViewDidEndScrollingAnimation
    [self.scrollView setContentOffset:offset animated:YES];
    
    // 注意13: 如果通过下列方式改变scrollView的contentOffset则不会调用scrollview的代理方法scrollViewDidEndScrollingAnimation
//    [UIView animateWithDuration:0.25 animations:^{
//        self.scrollView.contentOffset = offset;
//    [self.scrollView setContentOffset:<#(CGPoint)#>];
//    }];
}

- (void)tagClick
{
    XMGLogFunc
}

#pragma mark - 添加子控制器的view
- (void)addChildVcView
{
    // 子控制器的索引
    NSUInteger index = self.scrollView.contentOffset.x / self.scrollView.xmg_width;
    
    // 取出子控制器
    UIViewController *childVc = self.childViewControllers[index];
    
    // 注意点14: 判断view是否被加载过,可以用以下三种方法,如果加载过view就不用再次加载
    // 方法一: 如果子控制器的view有superview
//    if (childVc.view.superview) return;
    // 方法二: 如果子控制器的view添加到了window
//    if (childVc.view.window) return;
    // 方法三: 如果子控制器被加载过
    if ([childVc isViewLoaded]) return;
    // 方法一:
//    childVc.view.xmg_x = index * self.scrollView.xmg_width;
//    childVc.view.xmg_y = 0;
//    childVc.view.xmg_width = self.scrollView.xmg_width;
//    childVc.view.xmg_height = self.scrollView.xmg_height;
    // 方法二:
//    childVc.view.xmg_x = self.scrollView.contentOffset.x;
//    childVc.view.xmg_y = self.scrollView.contentOffset.y;
//    childVc.view.xmg_width = self.scrollView.xmg_width;
//    childVc.view.xmg_height = self.scrollView.xmg_height;
    // 方法三:
//    childVc.view.xmg_x = self.scrollView.bounds.origin.x;
//    childVc.view.xmg_y = self.scrollView.bounds.origin.y;
//    childVc.view.xmg_width = self.scrollView.bounds.size.width;
//    childVc.view.xmg_height = self.scrollView.bounds.size.height;
    // 方法四:
//    childVc.view.frame = CGRectMake(self.scrollView.bounds.origin.x, self.scrollView.bounds.origin.y, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
    // 方法五:
    // 注意15: scrollview.contentOffset.origin = scrollview.bounds
    // 注意16: 设置子控制的tableview完全覆盖scrollview而不是覆盖父控制器的view,再到子控制器里面设置tableview的self.tableView.contentInset = UIEdgeInsetsMake(64 + 35, 0, 49, 0),其次也可以设置scrollview的滚动条的inset属性self.tableView.scrollIndicatorInsets = self.tableView.contentInset, 让滚动条可以在中间范围滚动;
    // 所以,scrollview和子控制器的tableview是覆盖整个屏幕的,而tableview的contentInset则会让内容在导航条下显示,然后往上滚动也会有穿透效果
    childVc.view.frame = self.scrollView.bounds;
    [self.scrollView addSubview:childVc.view];
}

#pragma mark - <UIScrollViewDelegate>
/**
 * 在scrollView滚动动画结束时, 就会调用这个方法
 * 注意16: 前提: 使用setContentOffset:animated:或者scrollRectVisible:animated:方法让scrollView产生滚动动画
 */
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    // 滚动到某个界面时,判断是否添加子控制器的view到scrollview上
    [self addChildVcView];
}

/**
 * 在scrollView滚动动画结束时, 就会调用这个方法
 * 注意17: 前提: 人为拖拽scrollView产生的滚动动画
 */
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    // 选中\点击对应的按钮
    NSUInteger index = scrollView.contentOffset.x / scrollView.xmg_width;
    // 注意14:
    // 方法一: 拿到titleview中的button有两种方法,一种是从button的tag拿到,但是有一个问题,当使用tag时,是优先从父控件中拿的,而且父控件的tag默认是0,所以如果子控件的tag也是0,则会出错
    // 当index == 0时, viewWithTag:方法返回的就是self.titlesView
    //    XMGTitleButton *titleButton = (XMGTitleButton *)[self.titlesView viewWithTag:index];
    // 方法二: 通过subvious中拿到
    XMGTitleButton *titleButton = self.titlesView.subviews[index];
    
    
    // 注意18: 因为scrollview调用这个方法时,scrollview的contentOffset已经发生了改变,所以这时调用titleClick方法,里面并没有修改scrollview的contentOffset,故不会有动画,故不会调用crollViewDidEndScrollingAnimation:,因为后续要自己调用addChildVcView方法,添加子控件
    [self titleClick:titleButton];
    
    // 添加子控制器的view
    [self addChildVcView];
    
   
}
@end

2.其中一个子控制器中

- (void)viewDidLoad {
    [super viewDidLoad];
    
    XMGLogFunc
    // 注意1: 如果添加到scrollview上,由于有导航条的影响,会下移64,而且默认情况下,还会下移20,因此,禁止scrollview的自动调整,和重新设置tableview的内边距是最好的
    self.tableView.contentInset = UIEdgeInsetsMake(64 + 35, 0, 49, 0);
    // 注意2: 子控制器里面设置tableview的self.tableView.contentInset = UIEdgeInsetsMake(64 + 35, 0, 49, 0),其次也可以设置scrollview的滚动条的inset属性self.tableView.scrollIndicatorInsets = self.tableView.contentInset, 让滚动条可以在中间范围滚动;

    self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
}

3.自定义按钮中

#import "XMGTitleButton.h"

@implementation XMGTitleButton


- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 设置按钮颜色
        // self.selected = NO;
        [self setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
        // self.selected = YES;
        [self setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
        self.titleLabel.font = [UIFont systemFontOfSize:14];
    }
    return self;
}

// 注意3: 重写高亮的方法,使得点击时没有高亮的任何效果(一闪)
- (void)setHighlighted:(BOOL)highlighted {}

@end

补充:

  • 1.使用按钮的好处以及按钮disable属性和userInteractionEnabled属性的区别

// 注意0: 直接设置的话,程序一启动就加载了5个控制器,没有做到懒加载
- (void)setupTitlesView
{
    // 标题栏
    UIView *titlesView = [[UIView alloc] init];
//    titlesView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.2];
//    titlesView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.2];
    titlesView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.2];
    titlesView.frame = CGRectMake(0, 64, self.view.xmg_width, 35);
    [self.view addSubview:titlesView];
    
    // 添加标题
    NSArray *titles = @[@"全部", @"视频", @"声音", @"图片", @"段子"];
    NSUInteger count = titles.count;
    CGFloat titleButtonW = titlesView.xmg_width / count;
    CGFloat titleButtonH = titlesView.xmg_height;
    for (NSUInteger i = 0; i < count; i++) {
        // 注意1:创建按钮(这里可以用按钮,也可以用UIView添加手势,直接用按钮比较方便,还可以设置不同状态,不同的字体,监听方法的添加也方便)
        // 注意2:按钮有label和image属性,他们都是懒加载的,在没有设置的时候,是不显示的,所以只设置文字,不会有image
        XMGTitleButton *titleButton = [XMGTitleButton buttonWithType:UIButtonTypeCustom];
        [titleButton addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];
        [titlesView addSubview:titleButton];
        
        // 设置数据
        [titleButton setTitle:titles[i] forState:UIControlStateNormal];
        
        // 设置frame
        titleButton.frame = CGRectMake(i * titleButtonW, 0, titleButtonW, titleButtonH);
        titleButton.userInteractionEnabled
        // 此处代码放到了自定义cell中
        // 注意4: 如果设置按钮的状态为 titleButton.enabled = NO,则点击一次后,无法再次点击该按钮进行刷新
        // 注意5: titleButton的enabled属性和userInteractionEnabled属性是不同的,只有前者表示UIControlStateDisabled,后者只是仅仅不能点击,但是前者除了不能点击外,还可能改变按钮的样式,如改变按钮的颜色为灰色
        // titleButton.enabled = YES;
//        [titleButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
        // titleButton.enabled = NO;
//        [titleButton setTitleColor:[UIColor redColor] forState:UIControlStateDisabled];
    }
    
    // 按钮的选中颜色
    XMGTitleButton *lastTitleButton = titlesView.subviews.lastObject;
    NSLog(@"%@", [lastTitleButton titleColorForState:UIControlStateNormal]);
    
    // 底部的指示器
    UIView *indicatorView = [[UIView alloc] init];
    indicatorView.backgroundColor = [lastTitleButton titleColorForState:UIControlStateSelected];
    indicatorView.xmg_height = 1;
    indicatorView.xmg_y = titlesView.xmg_height - indicatorView.xmg_height;
    [titlesView addSubview:indicatorView];
    self.indicatorView = indicatorView;
}

  • 2.计算按钮标题的长度
#pragma mark - 监听点击
- (void)titleClick:(XMGTitleButton *)titleButton
{
    // 控制按钮状态
    self.selectedTitleButton.selected = NO;
    titleButton.selected = YES;
    self.selectedTitleButton = titleButton;
    
//    self.selectedTitleButton.enabled = YES;
//    titleButton.enabled = NO;
//    self.selectedTitleButton = titleButton;
    
//    [self.selectedTitleButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
//    [titleButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
//    self.selectedTitleButton = titleButton;

    // 指示器
    [UIView animateWithDuration:0.25 animations:^{
        // 注意1: 计算文字宽度(只能计算不换行的, 要换行的用其他方法)
//        CGFloat titleW = [titleButton.currentTitle sizeWithFont:titleButton.titleLabel.font].width;
//        CGFloat titleW = [titleButton.currentTitle sizeWithAttributes:@{NSFontAttributeName : titleButton.titleLabel.font}].width;
//        self.indicatorView.xmg_width = titleW;
        // 注意2: 这个一个可以计算文字宽度的方法,返回值是CGRect
//        [titleButton.currentTitle boundingRectWithSize:<#(CGSize)#> options:<#(NSStringDrawingOptions)#> attributes:<#(nullable NSDictionary<NSString *,id> *)#> context:<#(nullable NSStringDrawingContext *)#>];
//        
        // 注意2: 底部横线和按钮的label长度一样,所以可以用titleLabel的宽度来设置指示器的长度,这也是使用button的好处
        self.indicatorView.xmg_width = titleButton.titleLabel.xmg_width;
        self.indicatorView.xmg_centerX = titleButton.xmg_centerX;
    }];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容