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

 

20131311_ZiDM.png

在一个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(编译)

转载于:https://my.oschina.net/hejunbinlan/blog/714955

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值