2.封裝SWPTabBar方式一
接著我們思考如何進行封裝。前面已經將過了為什么要封裝, 和封裝達到的效果。這里我們主要有兩種封裝方式,分別是站在不同的角度上看待問題。雖然角度不同但是內部最核心的思想還是一樣的,就是屏蔽控件內部是如何構造的細節, 為外界提供簡單容易理解的接口。方式一: SWPTabBar屏蔽所有內在細節,只需提供要創建的控件個數。就可以創建出SWPTabBar的大體框架。接著有幾個方法可以用來設置TabBar內部按鈕的數據。簡單來說只需提供1. 設置按鈕個數。2. 設置按鈕數據的接口。
注意: 那么我們如何保證接口的健壯性, 要知道如果有用戶反復或多次設置按鈕個數,那么我們要做到的效果是重新構造TabBar新的內部,而不能造成按鈕的重復累加。這也是我們設計接口時候要考慮的一點,該接口是否方便使用, 時候多次使用會出錯等等許多細節。并不是所設計接口就是隨便的提供個方法的調用這么簡單,這需要經驗的積累。
首先看下面代碼和注釋:
-(void)viewDidLoad { [super viewDidLoad]; // 創建自定義的tabBar控件 SWPTabBar * tabBar = [[SWPTabBar alloc] init]; tabBar.frame = self.tabBar.frame; // 設置控件內部的按鈕個數 tabBar.numberOfBarButton = 5; // 控件代理對象, 用于監聽對象 tabBar.delegate = self; // 蓋在原來tabBar之上 [self.view addSubview: tabBar];}
看如上代碼, 我們的意圖很明顯,就是用我們自定義的SWPTabBar蓋在UITabBar上,造成一種以假亂真的效果。當然你也可以選擇把UITabBar刪了,在覆蓋上去,減少不必要的渲染。接著我們就要關注內部細節的實現。這才是我們要掌握的重點。先看如下主線
分析:1.首先我們可以選擇在initWithFrame中添加控件。為什么不在init呢,因為用戶可能會直接調用initWithFrame方法,這樣就不正確了。但是init方法其實還會調用initWithFrame方法,所以我們重寫initWithFrame方法是最保險的方式。2.必須在確定了SWPTabBar的尺寸之后再計算其子控件的位置和尺寸, 所以我們重寫layoutSubViews方法。
分析中的1和2代碼分別如下:
-(instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame: frame]) { [self setup] } return self;}-(void)setup { for (int i = 0; i < self.numberOfBarButton; i++) { SWPButton * button = [[SWPButton alloc] init]; // 設置按鈕不同狀態的圖片 [button setBackgroundImage:[UIImage imageNamed:@"TabBar1"] forState: UIControlStateNormal]; [button setBackgroundImage:[UIImage imageNamed:@"TabBar1Sel"] forState: UIControlStateSelected]; // 為每個按鈕綁定tag, 方便后面切換為不同的UINavigationController button.tag = i; // 添加監聽 [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchDown]; // 設置初始選中按鈕 if ( 0 == i) { [self buttonClicked: button]; } [self addSubview: button]; } // 設置斷言, 控件個數為按鈕個數 assert(self.subviews.count == self.numberOfBarButton);}
快速的瀏覽一下這段代碼,抓住起重點功能理解即可。為什么我們要抽出setup把添加構造控件內部的代碼都放在這, 其一、是為了提高代碼的復用。因為現在我們只是在initWithFrame中構造,當我們可用可能會提供xib的方式進行構造,在xib方式中,我們一般在awakeFromNib中進行控件的構造,你可以把它理解為相當于xib的初始化方法。其二、是為了在出錯的時候方便調試,一般一個函數或者方法的功能超過了12行以上不利于后面項目的維護,當然這也不能定死的看。像有的方法,剛好是20行已經是一個功能,而且也不能在抽取,這時候不要死腦筋非得把該方法進行拆分。拆分的是為了復用和調試。代碼不難理解,有疑問的可以給我留言。我們主要掌握的是整個的思路。具體細節需要你們自己進行代碼的編寫,遇到問題在解決,那才有所意義,不要在意細節。
以下代碼為重寫layoutSubViews實現內部子控件的位置尺寸的確定。
- (void)layoutSubviews { [super layoutSubviews]; // 按鈕的尺寸, Y坐標值 CGFloat buttonW = self.frame.size.width / self.numberOfBarButton; CGFloat buttonH = self.frame.size.height; CGFloat buttonY = 0.0; // 遍歷按鈕,設置尺寸、位置 for (int i = 0; i < self.numberOfBarButton; i++) { CGFloat buttonX = i * buttonW; SWPButton * button = self.subviews[i]; button.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH); button.backgroundColor = [UIColor redColor]; }}
分析:好了運行程序,自定義的SWPTabBar是不是不正常?為什么對應的按鈕個數,無法顯示出來。仔細思考下,不難發現。我們是在創建完SWPTabBar之后,才來設置按鈕的個數。也就是說此時SWPTabBar已經構建后了,我們此時設置的個數根本就沒有用!。那么怎么解決,我是通過重寫numberOfButtons的setter方法,將setup方法放在setter中。這樣,我們就可以再設置按鈕個數的時候,再來構造該控件的內部。此時發現了setup的好處沒。入果你把這些代碼直接方法initWithFrame中, 那么現在就必須先復制,將initWithFrame刪除,在粘貼到setter方法中。如果有多個自定義的控件,一個個這么做,我想你會瘋了的。問題:我們需要關注一個細節,什么不是所不要在意細節!該在意的要在意,不該在意的要忽略,要看關注點!就是如果用戶多次調用這個setter方法, 會造成按鈕的重復添加。這不是我們要的效果。所以我們需要在這之前移除SWPTabBar內部所有的控件。再進行添加。所以我實現了 clear方法,具體刪除細節看代碼,思路很簡單:逐個遍歷,刪除。
修改后的代碼如下
- (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame: frame]) { } return self;}- (void)setNumberOfBarButton:(NSUInteger)numberOfBarButton { _numberOfBarButton = numberOfBarButton; // 先刪除存在的按鈕 [self clear]; // 先添加按鈕 [self setup];}- (void)clear { [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];}
效果圖如下:
點擊跳轉按鈕的效果圖
3.改進我們知道, UITabBarController中的幾個子控制器是UINavagationController,當點擊下面SWPTabBar控件中的按鈕時候,進行子控制器的切換, 當點擊UINavigationController上面的按鈕進行跳轉時候,實際上是UINavigationController對其子控制器進行壓棧操作。現在我們想實現的效果是, 當UINavigationController切換其子控制器的時候, 新的控制器不顯示出UITabBar, 我們可以通過SB中操作來勾選新控制器的Hide Bar Buttom …來對UITabBar進行隱藏, 然而我們自定義的SWPTabBar并不是UITabBar的子控件而是其兄弟控件, 所以設置的效果是作用在UITabBar上,又因為SWPTabBar比較后面添加, 蓋在了上面, 所以是看不到效果的。此時我們只需要換種思路, 把SWPTabBar直接加進UITabBar控件中, 并完全覆蓋它即可實現這樣的效果。
修改的代碼如下:(關注第6行和第12行,相信不難理解)
- (void)viewDidLoad { [super viewDidLoad]; SWPTabBar * tabBar = [[SWPTabBar alloc] init]; tabBar.frame = self.tabBar.bounds; tabBar.numberOfBarButton = 5; tabBar.delegate = self; [self.tabBar addSubview: tabBar]; }
思考:手動 VS 代碼
我們的確可以通過勾選Hide Bar Buttom …來隱藏UITabBar,但是當控制器達到一定數量時候, 這種做法的做事效率太低, 這就好比快速排序和冒泡排序,這兩種排序在排序個數很少的時候, 冒泡排序取得的時間效率比快速排序還低, 但是當操作某個值后,快速排序有十分好的時間效率。依次類比, 我們也要知道代碼的方式, 這樣遇到熟練一多的情況, 可以幫助我們提高項目的開發速度。說了太多廢話,仁者見仁,智者見智。
實現具體細節如下: 1.通過自定義的SWPNavigationController重寫管理其子控制器棧結構的入棧方法。來攔截push操作 ,在push之前, 設置隱藏底部的UITabBar。 2.調用父類的入棧操作,完成父類自己的事情(將UIViewController入棧)。 代碼如下: 3.到sb中分別更改UINavigationController的關聯類
// SWPNavigationController- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { viewController.hidesBottomBarWhenPushed = YES; [super pushViewController:viewController animated:animated];}
改進后點擊跳轉按鈕的效果圖
可能你會發現我一直都沒講怎么實現SWPTabBar中如何切換到不同的UINavigationController,這個我們放在下一節介紹,現在我們只需要關注以上的重點,并理解之即可。
1.自定義類中,繼承 + 重寫攔截了要調用的方法。 2.UINavigationController與UITabBar管理子控制器的方式 3.如何自定義控件(setup、layoutSubViews) 4.如何更加全面的考慮設計的接口
新聞熱點
疑難解答