278
技術社區[雲棲]
《iPhone與iPad開發實戰—iOS經典應用剖析》連載五
RootViewController的h文件編寫完成我們接著編寫RootViewController的m文件如“代碼清單3-2 Password/Classes/RootViewController.m”所示。【代碼清單3-1】 Password/Classes/RootViewController.m
#import "RootViewController.h" #import "MainViewController.h" #import "FlipsideViewController.h" @implementation RootViewController @synthesize infoButton; @synthesize flipsideNavigationBar; @synthesize mainViewController; @synthesize flipsideViewController; - (void)viewDidLoad { MainViewController*viewController = [[MainViewController alloc]initWithNibName:@"MainView" bundle:nil]; self.mainViewController= viewController; [viewControllerrelease]; [self.viewinsertSubview:mainViewController.view belowSubview:infoButton]; } - (void)loadFlipsideViewController { FlipsideViewController*viewController = [[FlipsideViewController alloc]initWithNibName:@"FlipsideView" bundle:nil]; self.flipsideViewController= viewController; [viewControllerrelease]; //Set up the navigation bar UINavigationBar*aNavigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0, 0.0,320.0, 44.0)]; aNavigationBar.barStyle= UIBarStyleBlackOpaque; self.flipsideNavigationBar= aNavigationBar; [aNavigationBarrelease]; UIBarButtonItem*buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:selfaction:@selector(toggleView)]; UINavigationItem*navigationItem = [[UINavigationItem alloc] initWithTitle:@"PasswordGenerator"]; navigationItem.rightBarButtonItem= buttonItem; [flipsideNavigationBarpushNavigationItem:navigationItem animated:NO]; [navigationItemrelease]; [buttonItemrelease]; } - (IBAction)toggleView { /* This method is called when the info or Donebutton is pressed. It flips the displayed view from the main viewto the flipside view and vice-versa. */ if(flipsideViewController == nil) { [selfloadFlipsideViewController]; } UIView*mainView = mainViewController.view; UIView*flipsideView = flipsideViewController.view; [UIViewbeginAnimations:nil context:NULL]; [UIViewsetAnimationDuration:1]; [UIViewsetAnimationTransition:([mainView superview] ?UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransitionFlipFromLeft)forView:self.view cache:YES]; if([mainView superview] != nil) { [flipsideViewControllerviewWillAppear:YES]; [mainViewControllerviewWillDisappear:YES]; [mainViewremoveFromSuperview]; [infoButton removeFromSuperview]; [self.viewaddSubview:flipsideView]; [self.viewinsertSubview:flipsideNavigationBar aboveSubview:flipsideView]; [mainViewControllerviewDidDisappear:YES]; [flipsideViewControllerviewDidAppear:YES]; }else { [mainViewControllerviewWillAppear:YES]; [flipsideViewControllerviewWillDisappear:YES]; [flipsideViewremoveFromSuperview]; [flipsideNavigationBarremoveFromSuperview]; [self.viewaddSubview:mainView]; [self.viewinsertSubview:infoButton aboveSubview:mainViewController.view]; [flipsideViewControllerviewDidDisappear:YES]; [mainViewControllerviewDidAppear:YES]; } [UIViewcommitAnimations]; } -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ //Return YES for supported orientations return(interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning]; // Releases the view if it doesn't have a superview //Release anything that's not essential, such as cached data } - (void)dealloc { [infoButtonrelease]; [flipsideNavigationBarrelease]; [mainViewControllerrelease]; [flipsideViewControllerrelease]; [superdealloc]; } @end
m文件中方法很多,值得我們關注有3個:viewDidLoad、loadFlipsideViewController和toggleView,下分別介紹這個3個方法。
viewDidLoad是視圖加載完成還沒有顯示時候回調的方法,在本應用中該方法是創建一個主視圖控製器,下麵語句就是從一個給定nib文件名字創建一個控製器的通用方法:
MainViewController *viewController =[[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];
主視圖控製器創建完成需要把指針賦值給本類的mainViewController屬性,因為該屬性設置retain,這樣可以防止內存泄漏,接著再釋放剛才創建的viewController對象。
self.mainViewController= viewController;
[viewControllerrelease];
在viewDidLoad該方法最後,通過下麵的語句實現,將創建的主視圖控製器中的視圖放入到當前視圖中作為子視圖,並且位於按鈕下麵,在iOS中視圖可以有子視圖,子視圖擺放是有順序的,前後順序的不同會引起遮擋和影響事件響應。
[self.view insertSubview:mainViewController.viewbelowSubview:infoButton];
與– insertSubview:aboveSubview:類似的方法還有:
· –addSubview:,直接在前麵增加子視圖;
· –insertSubview:atIndex:,按照索引插入子視圖;
· –exchangeSubviewAtIndex:withSubviewAtIndex:,交換兩個子視圖的順序,常用於視圖切換。
loadFlipsideViewController方法主要作用就是創建背後視圖和上麵的導航欄,創建背後視圖控製器方法與主視圖控製器方法一樣,這裏不再多說了。在iOS中導航欄比較麻煩的控件,它涉及到導航欄(Navigation Bar)、導航項目(Navigation Item)和導航按鈕(Bar Button Item),在導航欄中包含導航項目,導航項目包含導航按鈕,導航項目可以有標題,導航按鈕可以有左右兩個按鈕,它們的關係如圖3-35所示。

因此我們需要創建這3個對象,首先創建導航欄使用下麵的語句:
UINavigationBar *aNavigationBar = [[UINavigationBaralloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
使用該方法可以通過指定一個矩形輪廓來創建一個導航欄,創建完成導航欄後還要指定它的樣式:
aNavigationBar.barStyle= UIBarStyleBlackOpaque;
創建完成導航欄接著創建導航按鈕,然後再創建導航項目,再把導航按鈕放到導航項目中。下麵語句是創建一個導航按鈕:
UIBarButtonItem *buttonItem = [[UIBarButtonItemalloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:selfaction:@selector(toggleView)];
在該語句中指定了按鈕的樣式,同時指定了按鈕點擊事件觸發的時候調用的方法toggleView。導航項目創建是通過下麵語句實現的:
UINavigationItem *navigationItem =[[UINavigationItem alloc] initWithTitle:@"Password Generator"];
在該語句中通過指定標題來構造一個導航項目,然後再通過下麵的語句把剛才創建的導航按鈕放在到導航項目中,作為它的右按鈕。
navigationItem.rightBarButtonItem = buttonItem;
最後還要把導航項目放入到導航欄中,這個目標是通過下麵語句實現的,並且要求動畫顯示:
[flipsideNavigationBar pushNavigationItem:navigationItemanimated:NO];
導航欄維護一個堆棧,通過發出pushNavigationItem:animated:消息把一個導航項目壓棧,發出popNavigationItemAnimated:消息把一個導航項目出棧,當前視圖顯示的是棧頂的導航項目,所以,壓棧可以進入下一級導航項目,而出棧可以返回以上一級導航項目。
toggleView方法是點擊主視圖的按鈕和背後視圖的Done按鈕時候觸發的方法,在該方法中實現了兩個視圖的切換。在該方法中主要涉及到兩個知識點,一個是UIView切換問題,另一個是UIView動畫問題。兩個問題是相伴而生的,在UIView切換的時候往往伴隨著動畫發生。下麵我們先看看UIView切換問題。
在視圖切換有很多種方式,可以采用模態視圖控製器切換、導航控製器切換和普通視圖控製器切換。在本應用中采用的是普通視圖控製器卻換,它是定義一個根控製器,通過根控製器來控製其它視圖交替切換。RootViewController就是視圖控製器,其中有自己的View對象(根視圖),當主視圖加載時候,將主視圖作為根視圖的子視圖放入,當主視圖向背後麵翻轉的時候,把主視圖從根視圖中移除掉,把背後視圖作為根視圖的子視圖放入。當背後麵向主視圖翻轉的時候,把背後視圖從根視圖中移除掉,把主視圖作為根視圖的子視圖放入,這樣反反複複。
采用模態視圖控製器可以參考蘋果官方文檔https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html。
下麵我們看看toggleView方法的代碼,該方法首先執行下麵的語句,實現初始化背後視圖控製器目的,這裏有一個判斷如果flipsideViewController為nil時候才去發出loadFlipsideViewController消息,可以防止多重加載。
if(flipsideViewController == nil) {
[selfloadFlipsideViewController];
}
接下來通過下麵語句從視圖控製器其中獲得主視圖和背後視圖,由於在viewDidLoad方法中創建了主視圖控製器,在loadFlipsideViewController方法中創建了背後視圖控製器,因此在這裏獲得的視圖對象不會是nil的。
UIView*mainView = mainViewController.view;
UIView*flipsideView = flipsideViewController.view;
在iOS中動畫有多種形式,這裏的視圖翻轉是屬於UIView級別動畫,UIView級別動畫必須從[UIView beginAnimations:nilcontext:NULL]開始到[UIViewcommitAnimations]結束。在本應用中與UIView級別動畫有關代碼如下:
[UIViewbeginAnimations:nil context:NULL];
[UIViewsetAnimationDuration:1];
[UIViewsetAnimationTransition:([mainView superview]
?UIViewAnimationTransitionFlipFromRight
: UIViewAnimationTransitionFlipFromLeft)forView:self.view cache:YES];
… …
[UIViewcommitAnimations];
setAnimationDuration:語句設置動畫持續時間,setAnimationTransition:forView: cache:方法中第一個參數定義動畫轉變類型,第二個參數是當前視圖對象,第三個參數是是否使用緩衝區。動畫轉變類型是指動畫樣式,其取值是UIViewAnimationTransition枚舉類型,UIViewAnimationTransition的成員有:
· UIViewAnimationTransitionNone,不指定過渡類型;
· UIViewAnimationTransitionFlipFromLeft,指定從左側翻轉;
· UIViewAnimationTransitionFlipFromRight,指定從右側翻轉;
· UIViewAnimationTransitionCurlUp,指定向上卷起;
· UIViewAnimationTransitionCurlDown,指定向下卷起。
雖然,在本應用中沒有使用,但是UIView級別動畫中還有一個重要方法setAnimationCurve:,該方法可以設置動畫曲線,動畫曲線是決定動畫進入和退出屏幕的方式。其它取值是UIViewAnimationCurve枚舉類型,UIViewAnimationCurve的成員有:
· UIViewAnimationCurveEaseInOut,淡入淡出,開始時候慢,慢變快,中間最快,然後變慢;
· UIViewAnimationCurveEaseIn,淡入,開始時候慢然後越來越快;
· UIViewAnimationCurveEaseOut,淡出,開始快然後越來越慢;
· UIViewAnimationCurveLinear,線性勻速,開始和結束是一個速度。
判斷語句if ([mainView superview] != nil) {}可以判斷當前的視圖是否為主視圖,因為隻有當前視圖是主視圖情況下主視圖才有父視圖,否則就是當前視圖就是背後視圖。如果當前視圖是主視圖時候代碼如下:
[flipsideViewControllerviewWillAppear:YES];
[mainViewControllerviewWillDisappear:YES];
[mainViewremoveFromSuperview];
[infoButton removeFromSuperview];
[self.viewaddSubview:flipsideView];
[self.viewinsertSubview:flipsideNavigationBar aboveSubview:flipsideView];
[mainViewControllerviewDidDisappear:YES];
[flipsideViewControllerviewDidAppear:YES];
上麵的這段代碼就是要實現從主視圖向背後麵翻轉,因此,需要在根視圖中移除主視圖[mainView removeFromSuperview],同時還移除了infoButton 對象[infoButton removeFromSuperview],它們兩個都是在主視圖上顯示的。 [self.viewaddSubview:flipsideView]方法把背後視圖添加到當前視圖。在通過 [self.viewinsertSubview:flipsideNavigationBar aboveSubview:flipsideView]語句把導航欄添加到背後視圖上麵,使得導航欄與背後視圖同是當前視圖的子視圖,隻不過是導航欄在前麵。
此外,代碼中還用到了控製器中視圖出現和消失的幾個事件方法:
· viewWillAppear:,通知視圖控製器,它的視圖將要可見;
· viewWillDisappear:,通知視圖控製器,它的視圖將要消失;
· viewDidDisappear:,通知視圖控製器,它的視圖已經消失;
· viewDidAppear:,通知視圖控製器,它的視圖已經可見。
事實上,這幾個方法經常在視圖控製器中被重寫,用於處理視圖的不同生命周期中會觸發的事件,它們與viewDidLoad和viewDidUnload事件類似,它們調用會更加頻繁觸發。其中viewDidLoad和viewWillAppear:很類似,viewDidLoad是視圖被創建時候觸發,接著視圖變成可見的時候會觸發viewWillAppear:事件,當視圖變換到別的視圖,視圖從可見變成不可見,再次回來時候,視圖從不可見變成可見viewDidLoad方法就不會觸發,而viewWillAppear:事件會觸發。
最後更新:2017-04-02 16:47:34