[LEAF Photo] 整个 app 的大部分功能都是在 LPCollageViewController 中实现的,随着时间的推移,这个 Controller 开始变得臃肿代码很长,记录下瘦身的各种策略。
ViewModel 及组合模式的使用
[LEAF Photo] 为用户提供了拼接图片的不同模板,模板中包含了选择照片的视图、文字视图以及线条等,模板以 json 文件方式保存,将这些元素显示到视图上简单的逻辑就是判断 json 中是否存在节点,如果存在则创建对应的元素,这样创建模板的过程就会变的臃肿。
借鉴下 MVVM 的方式,将 MVC 中的 C 拆分成 VM,使用 ViewModel 的方式处理展示,在利用组合的方式将每种元素的展示方法拆分到各自类中实现,这样创建元素的这个冗长的过程就可以被分散到每个元素的 ViewModel 类中了。
@interface CollageComponentViewModel : NSObject
- (void)showToView:(CollageContainerView *)view target:(id)target;
@end
@interface CollageViewModel : CollageComponentViewModel
@end
@implementation CollageViewModel
- (instancetype)initWithCollageModel:(CollageModel *)collageModel {
self = [super init];
if (self) {
_collageModel = collageModel;
}
return self;
}
- (void)showToView:(CollageContainerView *)view target:(id)target {
NSArray *textDecArray = self.collageModel.textDec;
if (textDecArray) {
[self showToView:view target:target array:textDecArray];
}
// ...
}
@end
@interface CollageTextDecViewModel : CollageComponentViewModel
@end
- (void)showToView:(CollageContainerView *)view target:(id)target {
CollageLabel *textLabel = [[CollageLabel alloc] initWithFrame:frame];
textLabel.text = self.collageTextDecModel.text;
textLabel.font = [UIFont fontWithName:self.collageTextDecModel.fontName size:fontSize];
// ...
[view addSubview:textLabel];
}
原本在 Controller 和 View 中的很多代码,被分散到 CollageTextDecViewModel 等 ViewModel 中去。
除了此例外,通常的应用中一些网络请求的处理和页面设置也可以使用 ViewModel 的方式。
将子视图独立成单独的类
@interface CollageViewController ()
@property (strong, nonatomic) CollageTextToolbar *textToolbar;
@end
@implementation CollageViewController
- (CollageTextToolbar *)textToolbar {
if (_textToolbar) {
return _textToolbar;
}
_textToolbar = [[CollageTextToolbar alloc] init];
[self.view addSubview:_textToolbar];
}
// somewhere call self.textToolbar to set its frame
@end
@implementation CollageTextToolbar
- (UIView *)editOuterView {
if (_editOuterView) {
return _editOuterView;
}
_editOuterView = [[UIView alloc] init];
[_editOuterView setBackgroundColor:[UIColor whiteColor]];
[self addSubview:_editOuterView];
return _editOuterView;
}
- (UITextView *)editingTextView {
if (_editingTextView) {
return _editingTextView;
}
_editingTextView = [[UITextView alloc] init];
_editingTextView.delegate = self;
[self.editOuterView addSubview:_editingTextView];
return _editingTextView;
}
@end
将视图的处理抽离出来,Controller 只负责初始化设置属性,这样 CollageTextToolbar 中大量的视图设置代码可以从 Controller 中移除。
CollageTextToolbar 中存在的按钮,如果只需要和本视图中的控件打交道最好,如果还需要 Controller 中的视图进行交互,使用协议即可。
Keyboard Manager
除了视图,一些功能也可以抽离成一个单独的处理类。
@implementation LPCollageKeyboardManager : NSObject
- (instancetype)initWithTextToolBar:(CollageTextToolbar *)textToolBar {
self = [super init];
if (self) {
_textToolBar = textToolBar;
}
return self;
}
- (void)addKeyboardObserver {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector( keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
}
- (void)removeKeyboardObserver {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
// ... 设置 self.textToolBar
}
- (void)keyboardWillShow:(NSNotification *)notification {
// ... 设置 self.textToolBar
}
@end
Navigator
另外在页面中很多按钮触发跳转逻辑,可以将 NavigationController 的跳转逻辑独立成一个类
@interface LPCollageNavigator
- (void)navigateToDoneController:(UIImage *)savedImage;
@end
@implementation LPCollageNavigator : NSObject
- (instancetype)initWithNavigationController:(UINavigationController *)navigationController {
self = [super init];
if (self) {
_navigationController = navigationController;
}
return self;
}
- (void)navigateToDoneController:(UIImage *)savedImage {
LPCollageDoneController *doneController = [[LPCollageDoneController alloc] initWithSavedImage:savedImage];
[self.navigationController pushViewController:doneController animated:YES];
}
@end
将 DataSource 和其他 Protocols 分离
另外可以参见 objc.ic 的 Controller 瘦身 文章为其瘦身
@implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
@end
@implementation CustomTableViewController
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
@end