前言app
因为以前项目比较急,因此对界面的布局没有太多的要求,遗留下不少的问题,如今一直在作重构的工做,并非说把代码写得如何的完美,而是把代码的逻辑梳理得更加的清楚,更加便于后期迭代开发或者是维护。重构分为了UI重构和逻辑重构,今天讲的是一个UI重构的问题,在项目中纠结了一段时间,选择何种方式来布局,会比较好管理;下面来讲一下个人想法;ide
(一)场景;函数
界面头部有一个tab切换的功能,中间部分的ui是提供给用户输入时间信息的,而后还有一个查询的按钮;底部是带tab的列表;主要要解决的问题就是头部和底部tab切换的逻辑管理。布局
(二)个人思路;ui
按照常理,如今不少市面上的app,只要包括有tab的列表大部分采用的是scrollview滚动切换,再配合tab点击的切换;还有一种就是使用addChildViewController,而后配合tab点击进行切换(主要的缺点就是,不能左右滑动切换)。个人这个场景里面显然的包含两级切换逻辑,两层切换逻辑都是用scrollView来控制的话,就会让人感受左右切换的效果太多,有点不太舒服;最终我采用的是头部tab使用addChildViewController来控制显示底部的带tab的列表控制器,而后底部的控制器中经过scrollView来控制其子控制器。atom
(三)关键代码;code
这个NTContainViewController控制器,就是底部带tab的容器控制器,能够设置子控制器以及tab标签。orm
#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:^{ rem
} 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的基本使用,上下拉刷新。