知識點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閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容