轉帖|行業資訊|編輯:龔雪|2016-07-25 10:15:04.000|閱讀 390 次
概述:隨著功能的累計,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@fc6vip.cn