在一个ViewController中,这些职责可以被统一放在#pragma区域中。但是,我们其实应该考虑将它拆分,并且放在更小的原件中。
数据源
数据源模式(Data Source Pattern)是一种用来隔离哪个对象对应哪个引导路径的逻辑的方式。尤其是在复杂的图标视图中,这个模式非常实用,可以用来移除View Controller里所有“哪些cell在特定条件下可见”的逻辑。如果你曾经写过这样的图标,经常需要对row和section的整数进行对比,那么数据源模式非常适合你。
数据源模式可以和UITableViewDataSource共存,但是我发现用这些对象对cell进行配置,其发挥的作用于管理引导路径时不太一样,因此我比较喜欢将两者分开。
这个简单的数据源模式使用实例,可以帮你处理分段逻辑:
@implementation SKSectionedDataSource : NSObject
- (instancetype)initWithObjects:(NSArray*)objects sectioningKey:(NSString *)sectioningKey {
self = [super init];
if (!self) return nil;
[self sectionObjects:objectswithKey:sectioningKey];
return self;
}
-(void)sectionObjects:(NSArray *)objects withKey:(NSString *)sectioningKey {
self.sectionedObjects = //section theobjects array
}
-(NSUInteger)numberOfSections {
return self.sectionedObjects.count;
}
-(NSUInteger)numberOfObjectsInSection:(NSUInteger)section {
return [self.sectionedObjects[section]count];
}
-(id)objectAtIndexPath:(NSIndexPath *)indexPath {
returnself.sectionedObjects[indexPath.section][indexPath.row];
}
@end
标准合成(Standard Composition)
苹果在发布iOS5的时候,一同推出了View Controller Containment API。你可以使用这个API对View Controller进行合成。如果你的ViewController由多个逻辑单元所构成,你可以考虑将其拆分。
在一个拥有header和grid视图的屏幕上,我们可以加载两个View Controller,然后将他们放在正确的位置上。
-(SKHeaderViewController *)headerViewController {
if (!_headerViewController) {
SKHeaderViewController*headerViewController = [[SKHeaderViewController alloc] init];
[selfaddChildViewController:headerViewController];
[headerViewControllerdidMoveToParentViewController:self];
[self.viewaddSubview:headerViewController.view];
self.headerViewController =headerViewController;
}
return _headerViewController;
}
-(SKGridViewController *)gridViewController {
if (!_gridViewController) {
SKGridViewController*gridViewController = [[SKGridViewController alloc] init];
[selfaddChildViewController:gridViewController];
[gridViewControllerdidMoveToParentViewController:self];
[self.viewaddSubview:gridViewController.view];
self.gridViewController =gridViewController;
}
return _gridViewController;
}
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect workingRect = self.view.bounds;
CGRect headerRect = CGRectZero, gridRect =CGRectZero;
CGRectDivide(workingRect, &headerRect,&gridRect, 44, CGRectMinYEdge);
self.headerViewController.view.frame = tagHeaderRect;
self.gridViewController.view.frame =hotSongsGridRect;
}
Smarter Views
如果你是在ViewController的类中对所有子视图进行分配,你可以考虑使用Smarter View。UIViewController默认情况下会使用UIView来浏览属性,但是你也可以用自己的视图去取代它。你可以使用-loadView作为接入点,前提是你要在那个方法中设定了self.view。
@implementationSKProfileViewController
- (void)loadView {
self.view = [SKProfileView new];
}
//...
@end
@implementationSKProfileView : NSObject
- (UILabel *)nameLabel {
if (!_nameLabel) {
UILabel *nameLabel = [UILabel new];
//configure font, color, etc
[self addSubview:nameLabel];
self.nameLabel = nameLabel;
}
return _nameLabel;
}
- (UIImageView*)avatarImageView {
if (!_avatarImageView) {
UIImageView * avatarImageView =[UIImageView new];
[self addSubview:avatarImageView];
self.avatarImageView = avatarImageView;
}
return _avatarImageView
}
-(void)layoutSubviews {
//perform layout
}
@end
你也可以重新定义@property(nonatomic) SKProfileView *view,因为它是一个比UIView更具体的类别,分析器会将self.view视为 SKProfileView,从而完成正确的处理。
Presenter模式
Presenter模式可以包裹模型对象,改变它的显示属性,并且公开那些已被改变的属性的消息。在其他一些情境中,它也被称为Presentation Model、Exhibit模式和ViewModel等。
@implementation SKUserPresenter : NSObject
-(instancetype)initWithUser:(SKUser *)user {
self = [super init];
if (!self) return nil;
_user = user;
return self;
}
- (NSString *)name{
return self.user.name;
}
- (NSString *)followerCountString{
if (self.user.followerCount == 0) {
return @"";
}
return [NSString stringWithFormat:@"%@followers", [NSNumberFormatterlocalizedStringFromNumber:@(_user.followerCount)numberStyle:NSNumberFormatterDecimalStyle]];
}
- (NSString*)followersString {
NSMutableString *followersString =[@"Followed by " mutableCopy];
[followersStringappendString:[self.class.arrayFormatter stringFromArray:[self.user.topFollowersvalueForKey:@"name"]];
return followersString;
}
+(TTTArrayFormatter*) arrayFormatter {
static TTTArrayFormatter *_arrayFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_arrayFormatter = [[TTTArrayFormatteralloc] init];
_arrayFormatter.usesAbbreviatedConjunction = YES;
});
return _arrayFormatter;
}
@end
最重要的是,模型对象本身不会被暴露。Presenter扮演了模型看门人的角色。这保证了View Controller无法绕开Presenter而直接访问模型。
Binding模式
Binding模式在变化的过程中会使用模型数据对视图进行更新。Cocoa非常适合使用这个模式,因为KVO能够观察模型,并且从模型中进行读取,在视图中完成写入。Cocoa Binding是这个模式的AppKit版本。Reactive Cocoa等第三方库也非常适合这个模式。
@implementationSKProfileBinding : NSObject
-(instancetype)initWithView:(SKProfileView *)view presenter:(SKUserPresenter*)presenter {
self = [super init];
if (!self) return nil;
_view = view;
_presenter = presenter;
return self;
}
- (NSDictionary*)bindings {
return @{
@"name":@"nameLabel.text",
@"followerCountString":@"followerCountLabel.text",
};
}
- (void)updateView{
[self.bindingsenumerateKeysAndObjectsUsingBlock:^(id presenterKeyPath, id viewKeyPath, BOOL*stop) {
id newValue = [self.presentervalueForKeyPath:presenterKeyPath];
[self.view setObject:newvalueforKeyPath:viewKeyPath];
}];
}
@end
(Note that oursimple presenter from above isn’t necessarily KVO-able, but it could be made tobe so.)
Interaction模式
View Controller变得体量过大的重要原因之一,就是actionSheet.delegate= self的滥用。在Smaitalk中,Controller对象的整个角色,就是接受用户输入,并且更新试图和模型。如今我们所使用的交互相对复杂,这些交互会要求我们在View Controller中写下大量的代码。
交互的过程通常开始与用户的最初输入(例如点击按钮)、可选的用户再次输入(例如“你确定要继续吗?”),之后程序或产生活动,例如网路请求和状态改变。这个操作其实可以完全包裹在Interaction Object之中。
@implementationSKProfileViewController
- (void)followButtonTapped:(id)sender{
self.followUserInteraction =[[SKFollowUserInteraction alloc] initWithUserToFollow:self.user delegate:self];
[self.followUserInteraction follow];
}
-(void)interactionCompleted:(SKFollowUserInteraction *)interaction {
[self.binding updateView];
}
//...
@end
@implementationSKFollowUserInteraction : NSObject <UIAlertViewDelegate>
-(instancetype)initWithUserToFollow:userdelegate:(id<InteractionDelegate>)delegate {
self = [super init];
if !(self) return nil;
_user = user;
_delegate = delegate;
return self;
}
- (void)follow {
[[[UIAlertView alloc] initWithTitle:nil
message:@"Are you sure you want to follow this user?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Follow", nil] show];
}
-(void)alertView:(UIAlertView *)alertViewclickedButtonAtIndex:(NSInteger)buttonIndex {
if ([alertView buttonTitleAtIndex:buttonIndex]isEqual:@"Follow"]) {
[self.user.APIGatewayfollowWithCompletionBlock:^{
[self.delegateinteractionCompleted:self];
}];
}
}
@end
Keyboard Manager
当键盘状态出现改变,视图的更新也会在View Controller中出现卡顿,但是使用KeyboardManager模式可以很好的解决这个问题。
@implementationSKNewPostKeyboardManager : NSObject
-(instancetype)initWithTableView:(UITableView *)tableView {
self = [super init];
if (!self) return nil;
_tableView = tableView;
return self;
}
- (void)beginObservingKeyboard{
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardDidHide:)name:UIKeyboardDidHideNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardWillShow:)name:UIKeyboardWillShowNotification object:nil];
}
-(void)endObservingKeyboard {
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardDidHideNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:selfname:UIKeyboardWillShowNotification object:nil];
}
-(void)keyboardWillShow:(NSNotification *)note {
CGRect keyboardRect = [[note.userInfoobjectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.tableView.contentInset.top,0.0f, CGRectGetHeight(keyboardRect), 0.0f);
self.tableView.contentInset =contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
}
-(void)keyboardDidHide:(NSNotification *)note {
UIEdgeInsets contentInset =UIEdgeInsetsMake(self.tableView.contentInset.top, 0.0f,self.oldBottomContentInset, 0.0f);
self.tableView.contentInset =contentInset;
self.tableView.scrollIndicatorInsets = contentInset;
}
@end
You can call-beginObservingKeyboard and -endObservingKeyboard from -viewDidAppear and-viewWillDisappear or wherever’s appropriate.在需要的时候,你也可以从-viewDidAppear和-viewWillDisappear中调取-beginObservingKeyboard和-endObservingKeyboard。
Navigator模式
通常情况下,视图间的切换是通过调取to -pushViewController:animated:来实现的。随着过渡效果越来越复杂,你可以将这个任务指定给Navigator对象来完成。尤其是在同时支持iPhone和iPad的应用中,视图切换需要根据设备屏幕尺寸的不同而改变。
@protocolSKUserNavigator <NSObject>
-(void)navigateToFollowersForUser:(SKUser *)user;
@end
@implementationSKiPhoneUserNavigator : NSObject<SKUserNavigator>
-(instancetype)initWithNavigationController:(UINavigationController*)navigationController {
self = [super init];
if (!self) return nil;
_navigationController =navigationController;
return self;
}
- (void)navigateToFollowersForUser:(SKUser*)user {
SKFollowerListViewController *followerList= [[SKFollowerListViewController alloc] initWithUser:user];
[self.navigationControllerpushViewController:followerList animated:YES];
}
@end
@implementationSKiPadUserNavigator : NSObject<SKUserNavigator>
-(instancetype)initWithUserViewController:(SKUserViewController*)userViewController {
self = [super init];
if (!self) return nil;
_userViewController = userViewController;
return self;
}
-(void)navigateToFollowersForUser:(SKUser *)user {
SKFollowerListViewController *followerList= [[SKFollowerListViewController alloc] initWithUser:user];
self.userViewController.supplementalViewController = followerList;
}
总结
从历史来看,苹果的SDK只包含最小数量的原件,但是随着越来越多的API使用,我们经常会让View Controller的体量变得越来越大。将ViewController的职责指定给其他方式去完成,我们可以更好的控制View Controller的体积。
原 文: 8 Patterns to Help You Destroy Massive View Controller
译 文:SDK.cn
作 者:Christian(编译)