文档章节

iOS项目重构

麦兜卖鱼丸
 麦兜卖鱼丸
发布于 2016/09/24 19:38
字数 1444
阅读 39
收藏 0

前言

由于之前项目比较急,所以对界面的布局没有太多的要求,遗留下很多的问题,现在一直在做重构的工作,并不是说把代码写得如何的完美,而是把代码的逻辑梳理得更加的清楚,更加便于后期迭代开发或者是维护。重构分为了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的基本使用,上下拉刷新。

© 著作权归作者所有

麦兜卖鱼丸
粉丝 12
博文 69
码字总数 69333
作品 0
桂林
iOS工程师
私信 提问
iOS三维菜单、调试工具、封装通讯录、网络框架、多种控件和动画等源码

iOS精选源码 一个调用系统通讯录和获取通讯录所有联系人的封装(http://www.code4app.com/thread-29726-1-1.html) ios scrollview嵌套tableview同向滑动(初级、进阶), 支持OC / Swift(http...

sunnyaigd
2018/05/15
56
0
专访链家网璩介业:实施组件化开发,首先应该搞定产品经理

小编语 本文为 DevLink 专访系列,本期采访嘉宾是 iDev 苹果开发者大会 iOS 应用架构组件化的讲师——璩介业。在即将到来的 iDev 大会上,他将和大家分享组件化的一些概念,和项目组件化过程...

DevLink
2016/10/28
670
0
Airbnb: React Native 从选择到放弃

Airbnb 最近在 Medium 上发布了一系列文章详细描述了 Airbnb 与 React Native 从选择到放弃的整个心路历程。 React Native at Airbnb The Technology Building a Cross-Platform Mobile Tea...

banxi
2018/06/20
0
0
一样的iOS开发程序员为什么有人4k有人40k?

前言 移动开发真正火起来其实就是最近这几年,iOS 开发技术因为发展也就才这么几年,所以值得做的事情还有很多,这就造成了每年苹果的 WWDC 都会推出一堆新的特性和 API。整体上来说,这对业...

原来是泽镜啊
2018/05/16
6
1
疯狂ios讲义之创建cocos2d项目(2)

cocos2d项目如何支持ARC iOS 5中所支持的全新特性ARC(Automatic Reference Counting)首次在iOS系统中提供了自动内存管理,从而避免因为对retain、release、autorelease这些命令的错误调用而...

博文视点
2014/04/10
955
1

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
16
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部