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