轉(zhuǎn)帖|行業(yè)資訊|編輯:龔雪|2016-07-25 10:15:04.000|閱讀 402 次
概述:隨著功能的累計,View Controller的體量會變得巨大。鍵盤管理、用戶輸入、數(shù)據(jù)變形、視圖分配——這些東西當(dāng)中哪個才是真正的View Controller范圍?哪些東西應(yīng)該指派給其他對象?在這篇文章中,我們將會探索將這些職責(zé)隔離進(jìn)其各自對象的方式。這樣做能幫助我們簡化代碼,讓代碼獲得更高的可讀性。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>

在一個ViewController中,這些職責(zé)可以被統(tǒng)一放在#pragma區(qū)域中。但是,我們其實應(yīng)該考慮將它拆分,并且放在更小的原件中。
數(shù)據(jù)源模式(Data Source Pattern)是一種用來隔離哪個對象對應(yīng)哪個引導(dǎo)路徑的邏輯的方式。尤其是在復(fù)雜的圖標(biāo)視圖中,這個模式非常實用,可以用來移除View Controller里所有“哪些cell在特定條件下可見”的邏輯。如果你曾經(jīng)寫過這樣的圖標(biāo),經(jīng)常需要對row和section的整數(shù)進(jìn)行對比,那么數(shù)據(jù)源模式非常適合你。
數(shù)據(jù)源模式可以和UITableViewDataSource共存,但是我發(fā)現(xiàn)用這些對象對cell進(jìn)行配置,其發(fā)揮的作用于管理引導(dǎo)路徑時不太一樣,因此我比較喜歡將兩者分開。
這個簡單的數(shù)據(jù)源模式使用實例,可以幫你處理分段邏輯:
@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
蘋果在發(fā)布iOS5的時候,一同推出了View Controller Containment API。你可以使用這個API對View Controller進(jìn)行合成。如果你的ViewController由多個邏輯單元所構(gòu)成,你可以考慮將其拆分。
在一個擁有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的類中對所有子視圖進(jìn)行分配,你可以考慮使用Smarter View。UIViewController默認(rèn)情況下會使用UIView來瀏覽屬性,但是你也可以用自己的視圖去取代它。你可以使用-loadView作為接入點,前提是你要在那個方法中設(shè)定了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模式在變化的過程中會使用模型數(shù)據(jù)對視圖進(jìn)行更新。Cocoa非常適合使用這個模式,因為KVO能夠觀察模型,并且從模型中進(jìn)行讀取,在視圖中完成寫入。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對象的整個角色,就是接受用戶輸入,并且更新試圖和模型。如今我們所使用的交互相對復(fù)雜,這些交互會要求我們在View Controller中寫下大量的代碼。
交互的過程通常開始與用戶的最初輸入(例如點擊按鈕)、可選的用戶再次輸入(例如“你確定要繼續(xù)嗎?”),之后程序或產(chǎn)生活動,例如網(wǎng)路請求和狀態(tài)改變。這個操作其實可以完全包裹在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
當(dāng)鍵盤狀態(tài)出現(xiàn)改變,視圖的更新也會在View Controller中出現(xiàn)卡頓,但是使用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
通常情況下,視圖間的切換是通過調(diào)取to -pushViewController:animated:來實現(xiàn)的。隨著過渡效果越來越復(fù)雜,你可以將這個任務(wù)指定給Navigator對象來完成。尤其是在同時支持iPhone和iPad的應(yīng)用中,視圖切換需要根據(jù)設(shè)備屏幕尺寸的不同而改變。
@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只包含最小數(shù)量的原件,但是隨著越來越多的API使用,我們經(jīng)常會讓View Controller的體量變得越來越大。將ViewController的職責(zé)指定給其他方式去完成,我們可以更好的控制View Controller的體積。
本文來源:
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@ke049m.cn