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

推荐阅读更多精彩内容