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;
}
#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;
}];
}