8个模式帮你消除iOS代码中的巨大View Controller

随着功能的累计,View Controller的体量会变得巨大。键盘管理、用户输入、数据变形、视图分配——这些东西当中哪个才是真正的View Controller范围?哪些东西应该指派给其他对象?在这篇文章中,我们将会探索将这些职责隔离进其各自对象的方式。这样做能帮助我们简化代码,让代码获得更高的可读性。

8个模式帮你消除iOS代码中的巨大View Controller

在一个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

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  -(instancetype)initWithUserToFollow:userdelegate:(id)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

Navigator模式

通常情况下,视图间的切换是通过调取to -pushViewController:animated:来实现的。随着过渡效果越来越复杂,你可以将这个任务指定给Navigator对象来完成。尤其是在同时支持iPhone和iPad的应用中,视图切换需要根据设备屏幕尺寸的不同而改变。

@protocolSKUserNavigator  -(void)navigateToFollowersForUser:(SKUser *)user; @end @implementationSKiPhoneUserNavigator : NSObject -(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 -(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的体积。

本文来源:SDK.cn

标签:

来源:慧都

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2016年6月22日
下一篇 2016年6月22日

相关推荐

发表回复

登录后才能评论