背景
項(xiàng)目需要開發(fā)一個(gè)游戲類型的殼版本,殼版本的UI已經(jīng)出來了,剩下的事情就是復(fù)制粘貼了,重復(fù)的工作太沒意思了,不如來點(diǎn)有挑戰(zhàn)的,把項(xiàng)目重構(gòu)一遍吧,順便復(fù)制粘貼下,這樣才不會(huì)那么枯燥無聊吧。好吧,說干就干,誰叫我姓張呢?項(xiàng)目重構(gòu)的地方有很多,這次會(huì)講到一個(gè)經(jīng)常用到的頁面切換控件的重構(gòu)以及實(shí)現(xiàn)一個(gè)簡單的Demo,后續(xù)會(huì)放出一些其他方面重構(gòu)的實(shí)踐經(jīng)驗(yàn)。
結(jié)果
項(xiàng)目Demo:PTPageKit
老生常談沒圖沒真相,還是先上圖:
圖片很挫,大概做的就是這么一個(gè)東西,上面的切換按鈕,下面的是按鈕對(duì)應(yīng)的頁面。點(diǎn)擊按鈕,下面的頁面會(huì)隨之變化。
分析
把系統(tǒng)分為三部分,Page 頭部
,Page 內(nèi)容
, Page 中間者
,每一部分對(duì)應(yīng)的對(duì)輸入和輸出進(jìn)行抽象,對(duì)各個(gè)部分進(jìn)行組件化。
Header分析
Header的輸入(公有接口部分):
- 選中Header Item
- 刷新Header視圖
- 滾動(dòng)到Header某個(gè)位置
Header的輸出(Delegate和DataSource部分):
- 已經(jīng)選中Header Item
- Header Item的數(shù)量
- Header Item展示的View
- Header Item的元數(shù)據(jù)
Body分析
Body的輸入(公有接口部分):
- 選中Body頁面
- 刷新Body視圖
Body的輸出(Delegate和DataSource部分):
- 已經(jīng)滾動(dòng)到某個(gè)Body
- Body移動(dòng)到某個(gè)位置
- 有多少個(gè)Body
- Body對(duì)應(yīng)的ViewController
中間者分析
中間者作為了Body和Header的Delegate和DataSource,為Body和Header提供數(shù)據(jù)并且連接了這兩者。中間者隱藏了實(shí)現(xiàn)的細(xì)節(jié),客戶端(使用者)最終只要和中間者交互即可,傳遞需要的參數(shù)交給中間者去處理,中間者會(huì)做好子元素的UI渲染和UI交互。
組件圖
下面是對(duì)這個(gè)系統(tǒng)的輸入輸出進(jìn)行抽象的組件圖。
Page 頭部
,Page 內(nèi)容
, Page 中間者
這三者依賴于抽象基類,沒有依賴具體的實(shí)現(xiàn)類,所以他們是相互獨(dú)立并且可替換的。對(duì)于某個(gè)具體的實(shí)現(xiàn),客戶端(使用者)只要和 Page 中間者
這個(gè)對(duì)象打交道,傳遞需要的參數(shù):頁面的Header元數(shù)據(jù)和頁面的ViewContoller數(shù)組即可,
實(shí)現(xiàn)
抽象的基類
抽象的基類是針對(duì)組件圖的交互實(shí)現(xiàn)了一個(gè)基本的框架,并不包含任何的UI實(shí)現(xiàn),定義了組件交互的規(guī)范。
Body
//
// PTBasePageBodyView.h
// PlushGame
//
// Created by aron on 2017/11/23.
// Copyright ? 2017年 aron. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol PTPageBodyViewDelegate, PTPageBodyViewDataSource;
@interface PTBasePageBodyView : UIView
@property (nonatomic, weak) id<PTPageBodyViewDelegate> delegate;
@property (nonatomic, weak) id<PTPageBodyViewDataSource> dataSource;
- (void)moveToIndex:(NSInteger)index;
- (void)reloadData;
@end
//______________________________________________________________________________________________________________
@protocol PTPageBodyViewDelegate <NSObject>
- (void)bodyView:(PTBasePageBodyView *)bodyView didSelectItemAtIndex:(NSInteger)index;
- (void)bodyView:(PTBasePageBodyView *)bodyView didScrollWithOffset:(CGFloat)offset;
@end
//_______________________________________________________________________________________________________________
@protocol PTPageBodyViewDataSource <NSObject>
@required
- (NSInteger)numberOfItemsInBodyView:(PTBasePageBodyView *)bodyView;
- (UIViewController *)bodyView:(PTBasePageBodyView *)bodyView viewControllerAtIndex:(NSInteger)index;
@end
Header
//
// PTBasePageHeaderView.h
// PlushGame
//
// Created by aron on 2017/11/23.
// Copyright ? 2017年 aron. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, PTPageHeaderStyle) {
PTPageHeaderStyleDefault = 0,
};
@protocol PTPageHeaderViewDelegate, PTPageHeaderViewDataSource;
@interface PTBasePageHeaderView : UIView
@property (assign, nonatomic) PTPageHeaderStyle headerStyle;
@property (nonatomic, weak) id<PTPageHeaderViewDelegate> delegate;
@property (nonatomic, weak) id<PTPageHeaderViewDataSource> dataSource;
- (void)moveToIndex:(NSInteger)index;
- (void)animateMoveToOffset:(CGFloat)offset;
- (void)reloadData;
- (NSInteger)currentIndex;
@end
//______________________________________________________________________________________________________________
@protocol PTPageHeaderViewDelegate <NSObject>
- (void)headerView:(PTBasePageHeaderView *)headerView didSelectItemAtIndex:(NSInteger)index;
@end
//_______________________________________________________________________________________________________________
@protocol PTPageHeaderViewDataSource <NSObject>
@required
- (NSInteger)numberOfItemsInHeaderView:(PTBasePageHeaderView *)headerView;
@optional
- (UIView *)pageView:(PTBasePageHeaderView *)headerView headerItemViewAtIndex:(NSInteger)index;
- (id)pageView:(PTBasePageHeaderView *)headerView headerItemAtIndex:(NSInteger)index;
@end
中間者
Body.h
//
// PTBasePageViewController.h
// PlushGame
//
// Created by aron on 2017/11/23.
// Copyright ? 2017年 aron. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "PTBasePageHeaderView.h"
#import "PTBasePageBodyView.h"
@protocol PTBasePageViewDataSource <NSObject>
// 需要添加到ViewController的View上,默認(rèn)的是self.view
- (UIView*)pt_container;
@end
@interface PTBasePageViewController : UIViewController
<PTPageHeaderViewDelegate, PTPageHeaderViewDataSource,
PTPageBodyViewDelegate, PTPageBodyViewDataSource>
@property (nonatomic, weak) id<PTBasePageViewDataSource> dataSource;
@property (nonatomic, strong) PTBasePageHeaderView *headerView;
@property (strong, nonatomic) PTBasePageBodyView *bodyView;
@property (nonatomic, strong) NSArray<id>* pageHeaderTitles;
@property (nonatomic, strong) NSArray<UIViewController*>* pageBodyViewControllers;
@property (nonatomic, assign) NSInteger defaultIndex;
- (void)reloadData;
- (void)preViewDidLoadContainer;
- (UIView*)container;
@end
Body.m
//
// PTBasePageViewController.m
// PlushGame
//
// Created by aron on 2017/11/23.
// Copyright ? 2017年 aron. All rights reserved.
//
#import "PTBasePageViewController.h"
@interface PTBasePageViewController ()
@end
@implementation PTBasePageViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self preViewDidLoadContainer];
[self.container addSubview:self.bodyView];
[self.container addSubview:self.headerView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)reloadData {
[self.headerView reloadData];
[self.bodyView reloadData];
}
- (void)preViewDidLoadContainer {
}
- (UIView*)container {
UIView* container = nil;
if ([self.dataSource respondsToSelector:@selector(pt_container)]) {
container = [self.dataSource pt_container];
} else {
container = self.view;
}
return container;
}
#pragma mark - ......::::::: Lazy load :::::::......
- (PTBasePageHeaderView *)headerView {
if (nil == _headerView) {
_headerView = [[PTBasePageHeaderView alloc] init];
_headerView.dataSource = self;
_headerView.delegate = self;
}
return _headerView;
}
- (PTBasePageBodyView *)bodyView {
if (nil == _bodyView) {
_bodyView = [[PTBasePageBodyView alloc] init];
_bodyView.dataSource = self;
_bodyView.delegate = self;
}
return _bodyView;
}
#pragma mark - ......::::::: PTPageHeaderViewDelegate, PTPageHeaderViewDataSource :::::::......
- (void)headerView:(PTBasePageHeaderView *)headerView didSelectItemAtIndex:(NSInteger)index {
[self.bodyView moveToIndex:index];
}
- (NSInteger)numberOfItemsInHeaderView:(PTBasePageHeaderView *)headerView {
return self.pageHeaderTitles.count;
}
- (UIView *)pageView:(PTBasePageHeaderView *)headerView headerItemViewAtIndex:(NSInteger)index {
return nil;
}
- (id)pageView:(PTBasePageHeaderView *)headerView headerItemAtIndex:(NSInteger)index {
return self.pageHeaderTitles[index];
}
#pragma mark - ......::::::: PTPageBodyViewDelegate, PTPageBodyViewDataSource :::::::......
- (void)bodyView:(PTBasePageBodyView *)bodyView didSelectItemAtIndex:(NSInteger)index {
[self.headerView moveToIndex:index];
}
- (void)bodyView:(PTBasePageBodyView *)bodyView didScrollWithOffset:(CGFloat)offset {
[self.headerView animateMoveToOffset:offset];
}
- (NSInteger)numberOfItemsInBodyView:(PTBasePageBodyView *)bodyView {
return self.pageBodyViewControllers.count;
}
- (UIViewController *)bodyView:(PTBasePageBodyView *)bodyView viewControllerAtIndex:(NSInteger)index {
if (index >= 0 && index < self.pageBodyViewControllers.count) {
return self.pageBodyViewControllers[index];
}
return nil;
}
@end
具體的實(shí)現(xiàn)
具體的實(shí)現(xiàn)需要繼承 PTBasePageBodyView
PTBasePageHeaderView
PTBasePageViewController
實(shí)現(xiàn)一些UI和交互的細(xì)節(jié),比如Header中按鈕的布局和樣式等。可以參考項(xiàng)目中的一個(gè)子模塊 PTSimplePageKitImpl
,打開 Example Demo項(xiàng)目既可以看到效果,具體的實(shí)現(xiàn)不在贅述。
后記
項(xiàng)目Demo:PTPageKit