前言
由于之前项目比较急,所以对界面的布局没有太多的要求,遗留下很多的问题,现在一直在做重构的工作,并不是说把代码写得如何的完美,而是把代码的逻辑梳理得更加的清楚,更加便于后期迭代开发或者是维护。重构分为了UI重构和逻辑重构,今天讲的是一个UI重构的问题,在项目中纠结了一段时间,选择何种方式来布局,会比较好管理;下面来说一下我的想法;
(一)场景;
界面头部有一个tab切换的功能,中间部分的ui是提供给用户输入时间信息的,然后还有一个查询的按钮;底部是带tab的列表;主要要解决的问题就是头部和底部tab切换的逻辑管理。
(二)我的思路;
按照常理,现在很多市面上的app,只要包括有tab的列表大部分采用的是scrollview滚动切换,再配合tab点击的切换;还有一种就是使用addChildViewController,然后配合tab点击进行切换(主要的缺点就是,不能左右滑动切换)。我的这个场景里面显然的包含两级切换逻辑,两层切换逻辑都是用scrollView来控制的话,就会让人感觉左右切换的效果太多,有点不太舒服;最终我采用的是头部tab使用addChildViewController来控制显示底部的带tab的列表控制器,然后底部的控制器中通过scrollView来控制其子控制器。
(三)关键代码;
这个NTContainViewController控制器,就是底部带tab的容器控制器,可以设置子控制器以及tab标签。
#import <UIKit/UIKit.h>
@interface NTContainViewController : UIViewController
- (instancetype)initWithTitle:(NSString *)title andSubTitles:(NSArray *)subTitles andControllers:(NSArray *)controllers;
- (instancetype)initWithTitle:(NSString *)title andSubTitles:(NSArray *)subTitles andControllers:(NSArray *)controllers topOffset:(CGFloat)topOffset;
@property (nonatomic, assign) CGFloat topOffset;
- (void)refreshTableView;
#import "NTContainViewController.h"
#import "NTSubViewController.h"
#import "NTTitleBarView.h"
@interface NTContainViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *containScroll;
@property (nonatomic, strong) NSArray *controllers;
@property (nonatomic, strong) NSArray *subTitles;
@property (nonatomic, strong) NSString *navigationTitle;
@property (nonatomic, strong) NTTitleBarView *titleBarView;
@property (nonatomic, assign) NSInteger currentIndex;
@end
@implementation NTContainViewController
- (instancetype)initWithTitle:(NSString *)title andSubTitles:(NSArray *)subTitles andControllers:(NSArray *)controllers {
return [self initWithTitle:title andSubTitles:subTitles andControllers:controllers topOffset:0];
}
- (instancetype)initWithTitle:(NSString *)title andSubTitles:(NSArray *)subTitles andControllers:(NSArray *)controllers topOffset:(CGFloat)topOffset {
self = [super init];
if (self) {
self.subTitles = subTitles;
self.navigationTitle = title;
self.controllers = controllers;
self.topOffset = topOffset;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
if (_navigationTitle) {
self.navigationItem.title = _navigationTitle;
}
self.currentIndex = 0;
CGFloat titleBarHieght = 36.0f;
self.titleBarView = [[NTTitleBarView alloc] initWithFrame:({
CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, titleBarHieght);
frame;
}) titles:_subTitles];
[self.view addSubview:_titleBarView];
self.containScroll = [[UIScrollView alloc] initWithFrame:({
CGRect frame = CGRectMake(0, titleBarHieght, self.view.frame.size.width, self.view.frame.size.height - titleBarHieght);
frame;
})];
_containScroll.delegate = self;
_containScroll.bounces = NO;
_containScroll.showsHorizontalScrollIndicator = NO;
_containScroll.pagingEnabled = YES;
[self.view addSubview:_containScroll];
if (_controllers) {
[_controllers enumerateObjectsUsingBlock:^(UIViewController *controller, NSUInteger idx, BOOL * _Nonnull stop) {
[self addChildViewController:controller];
CGRect childRect = CGRectMake(self.view.frame.size.width * idx, 0, self.view.frame.size.width, self.view.frame.size.height - 64 - 100 - 36);
controller.view.frame = childRect;
[self.containScroll addSubview:controller.view];
}];
}
_containScroll.contentSize = CGSizeMake(self.view.frame.size.width * _controllers.count, _containScroll.frame.size.height);
__weak typeof(_containScroll) weakScroll = _containScroll;
_titleBarView.titleButtonClicked = ^(NSUInteger index) {
[weakScroll setContentOffset:CGPointMake(self.view.frame.size.width * index, 0) animated:NO];
_currentIndex = index;
};
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self scrollToStop:YES];
}
- (void)scrollToStop:(BOOL)stop {
CGFloat offsetX = _containScroll.contentOffset.x;
CGFloat scrollWidth = _containScroll.frame.size.width;
NSInteger focusIndex = (offsetX + (scrollWidth / 2)) / scrollWidth;
if (stop) {
[self titleBarViewButtonsAtIndex:focusIndex];
_currentIndex = focusIndex;
}
}
- (void)titleBarViewButtonsAtIndex:(NSInteger)index {
UIButton *currentButton = _titleBarView.subviews[index];
[_titleBarView.subviews enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL * _Nonnull stop) {
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}];
[currentButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
}
- (void)refreshTableView {
NTSubViewController *currentVC = (NTSubViewController *)_controllers[_currentIndex];
[currentVC refreshTableViewWithType:[NSString stringWithFormat:@"tableView%ld",_currentIndex]];
}
@end
下面的这个代码,是写在父视图控制器上面的,有tab切换的view,查询按钮,并将上面说到的NTContainViewController控制器当做子控制器并进行管理,通过
[strongSelf transitionFromViewController:strongSelf.currentVC toViewController:newViewController duration:0.3f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
} completion:^(BOOL finished) {
[newViewController didMoveToParentViewController:strongSelf];
[strongSelf.currentVC willMoveToParentViewController:nil];
[strongSelf.currentVC removeFromParentViewController];
strongSelf.currentVC = newViewController;
}];函数进行切换。
具体代码;
#import "NTTestViewController.h"
#import "NTContainViewController.h"
#import "NTSubViewController.h"
#import "NTTitleBarView.h"
@interface NTTestViewController () {
}
@property (nonatomic, strong) NTTitleBarView *testBarView;
@property (nonatomic, strong) NSArray *controllers;
@property (nonatomic, strong) UIViewController *currentVC;
@property (nonatomic, strong) NSInteger currentIndex;
@end
@implementation NTTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"tab切换";
self.view.backgroundColor = [UIColor whiteColor];
self.testBarView = [[NTTitleBarView alloc] initWithFrame:({
CGRect frame = CGRectMake(0, 64, self.view.frame.size.width, 36);
frame;
}) titles:@[@"左边", @"右边"]];
[self.view addSubview:_testBarView];
UIButton *searchButton = [UIButton buttonWithType:UIButtonTypeCustom];
searchButton.frame = CGRectMake(0, 120, self.view.frame.size.width, 30);
[searchButton setTitle:@"查询" forState:UIControlStateNormal];
searchButton.backgroundColor = [UIColor lightGrayColor];
[searchButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[searchButton addTarget:self action:@selector(excuteCode) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:searchButton];
[self addChildVC];
__weak typeof(self) weakSelf = self;
__strong typeof(self) strongSelf = weakSelf;
_testBarView.titleButtonClicked = ^(NSUInteger index) {
if (strongSelf.currentIndex != index) {
UIViewController *newViewController = strongSelf.controllers[index];
[strongSelf addChildViewController:newViewController];
[strongSelf transitionFromViewController:strongSelf.currentVC toViewController:newViewController duration:0.3f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
} completion:^(BOOL finished) {
[newViewController didMoveToParentViewController:strongSelf];
[strongSelf.currentVC willMoveToParentViewController:nil];
[strongSelf.currentVC removeFromParentViewController];
strongSelf.currentVC = newViewController;
strongSelf.currentIndex = index;
}];
}
};
}
- (void)addChildVC {
NTSubViewController *subVC1 = [[NTSubViewController alloc] initWithTitle:@"test1"];
NTSubViewController *subVC2 = [[NTSubViewController alloc] initWithTitle:@"test2"];
NTSubViewController *subVC3 = [[NTSubViewController alloc] initWithTitle:@"test3"];
NTContainViewController *testVC1 = [[NTContainViewController alloc] initWithTitle:@"test" andSubTitles:@[@"test1",@"test2",@"test3"] andControllers:@[subVC1, subVC2, subVC3] topOffset:100.0f];
testVC1.view.frame = CGRectMake(0, 64 + 100, self.view.frame.size.width, self.view.frame.size.height - 100 - 64);
NTSubViewController *subVC4 = [[NTSubViewController alloc] initWithTitle:@"test4"];
NTSubViewController *subVC5 = [[NTSubViewController alloc] initWithTitle:@"test5"];
NTSubViewController *subVC6 = [[NTSubViewController alloc] initWithTitle:@"test6"];
NTContainViewController *testVC2 = [[NTContainViewController alloc] initWithTitle:@"test" andSubTitles:@[@"test4",@"test5",@"test6"] andControllers:@[subVC4, subVC5, subVC6] topOffset:100.0f];
testVC2.view.frame = CGRectMake(0, 64 + 100, self.view.frame.size.width, self.view.frame.size.height - 100 - 64);
self.controllers = @[testVC1, testVC2];
self.currentVC = testVC1;
[self addChildViewController:_currentVC];
[self.view addSubview:_currentVC.view];
}
- (void)excuteCode {
NTContainViewController *currentVC = (NTContainViewController *)_currentVC;
[currentVC refreshTableView];
}
@end
(四)还有一个关键的地方,那就是父子控制器之间的交互;
因为这个界面的主要功能就是通过主视图的查询按钮,操作底部控制器(NTContainViewController控制器中的子控制器),当然最简单的方法就是发送通知,子控制器接收通知就行;但是我采用的是另外的方法,就是通过NTContainViewController这个容器控制器作为桥梁,主控制器发送消息至当前显示的NTContainViewController容器控制器,在通过这个容器控制器找到当前显示的子控制器,然后发送消息到这个控制器。上面的代码- (void)excuteCode、- (void)refreshTableView、- (void)refreshTableViewWithType:(NSString *)type;一层一层往下传递发送消息的。
总结
整个解决问题的思路,即解决了tab联动切换的问题,也解决了父控制器向子控制器发送消息的问题,项目中遇到的问题就能够解决,剩下的就只有子控制器的布局以及获取数据的部分了,而这个部分所涉及的内容已经很少,就是tableview的基本使用,上下拉刷新。