转帖|行业资讯|编辑:龚雪|2016-07-25 10:15:04.000|阅读 356 次
概述:随着功能的累计,View Controller的体量会变得巨大。键盘管理、用户输入、数据变形、视图分配——这些东西当中哪个才是真正的View Controller范围?哪些东西应该指派给其他对象?在这篇文章中,我们将会探索将这些职责隔离进其各自对象的方式。这样做能帮助我们简化代码,让代码获得更高的可读性。
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
在一个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
苹果在发布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; }
如果你是在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模式可以包裹模型对象,改变它的显示属性,并且公开那些已被改变的属性的消息。在其他一些情境中,它也被称为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模式在变化的过程中会使用模型数据对视图进行更新。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
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
当键盘状态出现改变,视图的更新也会在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
通常情况下,视图间的切换是通过调取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的体积。
本文来源:
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn