轉(zhuǎn)帖|行業(yè)資訊|編輯:龔雪|2016-07-25 10:15:04.000|閱讀 390 次
概述:隨著功能的累計(jì),View Controller的體量會(huì)變得巨大。鍵盤管理、用戶輸入、數(shù)據(jù)變形、視圖分配——這些東西當(dāng)中哪個(gè)才是真正的View Controller范圍?哪些東西應(yīng)該指派給其他對(duì)象?在這篇文章中,我們將會(huì)探索將這些職責(zé)隔離進(jìn)其各自對(duì)象的方式。這樣做能幫助我們簡(jiǎn)化代碼,讓代碼獲得更高的可讀性。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
在一個(gè)ViewController中,這些職責(zé)可以被統(tǒng)一放在#pragma區(qū)域中。但是,我們其實(shí)應(yīng)該考慮將它拆分,并且放在更小的原件中。
數(shù)據(jù)源模式(Data Source Pattern)是一種用來(lái)隔離哪個(gè)對(duì)象對(duì)應(yīng)哪個(gè)引導(dǎo)路徑的邏輯的方式。尤其是在復(fù)雜的圖標(biāo)視圖中,這個(gè)模式非常實(shí)用,可以用來(lái)移除View Controller里所有“哪些cell在特定條件下可見(jiàn)”的邏輯。如果你曾經(jīng)寫過(guò)這樣的圖標(biāo),經(jīng)常需要對(duì)row和section的整數(shù)進(jìn)行對(duì)比,那么數(shù)據(jù)源模式非常適合你。
數(shù)據(jù)源模式可以和UITableViewDataSource共存,但是我發(fā)現(xiàn)用這些對(duì)象對(duì)cell進(jìn)行配置,其發(fā)揮的作用于管理引導(dǎo)路徑時(shí)不太一樣,因此我比較喜歡將兩者分開(kāi)。
這個(gè)簡(jiǎn)單的數(shù)據(jù)源模式使用實(shí)例,可以幫你處理分段邏輯:
@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的時(shí)候,一同推出了View Controller Containment API。你可以使用這個(gè)API對(duì)View Controller進(jìn)行合成。如果你的ViewController由多個(gè)邏輯單元所構(gòu)成,你可以考慮將其拆分。
在一個(gè)擁有header和grid視圖的屏幕上,我們可以加載兩個(gè)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的類中對(duì)所有子視圖進(jìn)行分配,你可以考慮使用Smarter View。UIViewController默認(rèn)情況下會(huì)使用UIView來(lái)瀏覽屬性,但是你也可以用自己的視圖去取代它。你可以使用-loadView作為接入點(diǎn),前提是你要在那個(gè)方法中設(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,因?yàn)樗且粋€(gè)比UIView更具體的類別,分析器會(huì)將self.view視為 SKProfileView,從而完成正確的處理。
Presenter模式可以包裹模型對(duì)象,改變它的顯示屬性,并且公開(kāi)那些已被改變的屬性的消息。在其他一些情境中,它也被稱為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
最重要的是,模型對(duì)象本身不會(huì)被暴露。Presenter扮演了模型看門人的角色。這保證了View Controller無(wú)法繞開(kāi)Presenter而直接訪問(wèn)模型。
Binding模式在變化的過(guò)程中會(huì)使用模型數(shù)據(jù)對(duì)視圖進(jìn)行更新。Cocoa非常適合使用這個(gè)模式,因?yàn)镵VO能夠觀察模型,并且從模型中進(jìn)行讀取,在視圖中完成寫入。Cocoa Binding是這個(gè)模式的AppKit版本。Reactive Cocoa等第三方庫(kù)也非常適合這個(gè)模式。
@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變得體量過(guò)大的重要原因之一,就是actionSheet.delegate= self的濫用。在Smaitalk中,Controller對(duì)象的整個(gè)角色,就是接受用戶輸入,并且更新試圖和模型。如今我們所使用的交互相對(duì)復(fù)雜,這些交互會(huì)要求我們?cè)赩iew Controller中寫下大量的代碼。
交互的過(guò)程通常開(kāi)始與用戶的最初輸入(例如點(diǎn)擊按鈕)、可選的用戶再次輸入(例如“你確定要繼續(xù)嗎?”),之后程序或產(chǎn)生活動(dòng),例如網(wǎng)路請(qǐng)求和狀態(tài)改變。這個(gè)操作其實(shí)可以完全包裹在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)改變,視圖的更新也會(huì)在View Controller中出現(xiàn)卡頓,但是使用KeyboardManager模式可以很好的解決這個(gè)問(wèn)題。
@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
通常情況下,視圖間的切換是通過(guò)調(diào)取to -pushViewController:animated:來(lái)實(shí)現(xiàn)的。隨著過(guò)渡效果越來(lái)越復(fù)雜,你可以將這個(gè)任務(wù)指定給Navigator對(duì)象來(lái)完成。尤其是在同時(shí)支持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; }
從歷史來(lái)看,蘋果的SDK只包含最小數(shù)量的原件,但是隨著越來(lái)越多的API使用,我們經(jīng)常會(huì)讓View Controller的體量變得越來(lái)越大。將ViewController的職責(zé)指定給其他方式去完成,我們可以更好的控制View Controller的體積。
本文來(lái)源:
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn