你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 談談組件封裝的思路和實現

談談組件封裝的思路和實現

編輯:IOS開發基礎

本文是投稿文章,作者:潘晟
原文地址


前兩天面試了一個應聘者,他的演示項目裡有廣告輪播功能。恰好之前我封裝過一個實現了此功能的控件,於是就順著他廣告輪播的實現一直往下聊,從需求的抽象一直聊到各種實現的細節和需要考慮的問題等等。組件的封裝是開發中比較有趣的一件事。今天我們就拿輪播控件舉例,聊聊組件的封裝。

授人予漁先要授人予魚。先給出魚(PSCarouselView),再慢慢講漁。

PSCarouselView.gif

分析需求

一般來說,我們在封裝組件的時候,會先思考以下幾點

  • 這個組件要做什麼

  • 這個組件至少需要知道哪些信息

  • 這個組件會反饋哪些信息

這個階段,我們還不會考慮組件的具體實現,僅僅對其做抽象。我們要做的東西不僅僅是適用於單個項目的,而應該是通用的,可以適應大部分同種類需求的。

Scene I

BA說,客戶要在首頁加個輪播。於是半天後設計師出了張圖,看起來大概像這樣:

-----1.jpg

我們按照上面的思路,拆分一下這個需求。

這個組件要做什麼?

顯而易見的:

  1. 展示多張圖片

  2. 可以向左向右翻頁

  3. PageControl的狀態會根據圖片的滾動而相應改變

隱含可能要做的:

  1. 支持左右兩側無限循環滾動

  2. 自動輪播

  3. 支持手動滑動

  4. 支持點擊並進行相關的響應

  5. 圖片的緩存

這個組件至少需要知道哪些信息?

一個封裝得優秀的復雜度不高組件就像一個魔法盒子,只需要觸發啟動開關,就可以達到你期待的效果。極簡的觸發參數和條件是組件封裝的精髓。在內容型App中,輪播圖一般會用作推薦內容展示區域。在O2O類App中,輪播圖一般會用作廣告位。因此輪播區域圖片的內容絕大多數都是動態的。在一般的C/S開發中,客戶端要獲取存儲在服務器上的圖片會獲取它的URL,然後在需要的時候根據URL異步地加載這些圖片。因此,我們找出了的輪播空間第一個必備條件:

一個圖片URL地址數組。

@property (nonatomic, strong) NSArray *imageURLs;/**< 必須賦值。只要給這個imageURL賦值,會自動獲取圖片*/

這個時候我們已經可以根據數組內的URL,數組內URL的數量,完成輪播效果了。但還不夠完善。試想,當數組為空的時候,我們的輪播控件是一個什麼狀態?網絡比較慢,圖片還在加載中的時候,我們的輪播控件是什麼狀態?就目前來說----空白一片。

每個App都有每個App的設計,但無論如何誰也不會容忍首頁最醒目的部分出現一大片空白,因此占位圖是必須的。我們告訴我們的控件,“沒有圖片的時候別傻愣著,顯示這張圖”。必備條件二:

占位圖

@property (nonatomic, strong) UIImage *placeholder;/**< 沒有輪播圖時的占位圖*/

有了以上信息我們的輪播控件已經可以在運行得比較好了。但是缺少了一定的定制性,能滿足的需求面還不夠廣。比如說,讓控件在需要自動輪播的地方自動輪播,在不需要自動輪播的地方保留無限滾動的特性,可以手動撥動?(之前有個產品提過這樣的需求)比如說,這個控件是否應該提供可選的PageControl實現?依據設計師給的圖不同,我們可能需要實現不同的PageControl,不管是顏色,布局,還是其可見隱藏都可能會有不同。這一部分復雜度不應該歸到輪播控件中。但考慮到一些快速開發的需求,控件還是提供了一個默認選項。這些都是讓控件變得更加可配置和靈活(同時也變得復雜)的可選信息。綜上我們大概可以整理出以下屬性:

@property (nonatomic,getter=isAutoMoving)      BOOL autoMoving;/**< 是否自動輪播,默認為NO*/
@property (nonatomic,getter=isShowPageControl) BOOL showPageControl;/**< 是否展示默認的PageControl,默認為YES*/

這個組件會反饋哪些信息?

上一點中,如果使用者需要自己實現PageControl,那麼當前控件滾動到哪一頁了,應該給出反饋。

不論在O2O的App中,還是資訊類App中,用戶點擊輪播必定會有相對應的響應,例如之乎日報點擊跳轉到對應的文章,淘寶點擊查看大圖等。因此用戶點擊的信息也必須反饋出來,以讓控制器根據用戶的操作進行不同的響應。

View層對Controller層的反饋一般有兩種,一種是Target-Action,一種是Delegate。此處我們類似UITableView,給出一定的代理方法。遵循蘋果的設計規范,讓使用的開發者容易上手。

@protocol PSCarouselDelegate
@optional
/**
 *  告訴代理滾動到哪一頁了
 *
 *  @param carousel self
 *  @param page     已經計算好,直接使用即可
 */
- (void)carousel:(PSCarouselView *)carousel didMoveToPage:(NSUInteger)page;

/**
 *   告訴代理用戶點擊了某一頁
 *
 *  @param carousel
 *  @param index  imageURL的index  
 */
- (void)carousel:(PSCarouselView *)carousel didTouchPage:(NSUInteger)page;
@end

至此,輪播組件的架子已經明晰,剩下的就是如何實現,以及實現的時候需要考慮的一些細節。

實現過程

選擇基類

蘋果的UIKit提供了非常多優秀的類,可以作為我們輪播視圖的基類。根據以上的分析,我們根據直覺,立馬可以聯想到以下三個類: UIPageViewController、UIScrollView、UICollectionView。

UIPageViewController提供了非常好的翻頁封裝,僅需要指定翻頁的方向,和子ViewController的數組,就可以提供類似輪播的功能。並且UIPageViewController提供了供翻頁的接口和必要的反饋信息,定義在UIPageViewControllerDelegate協議中。用UIPageViewController,我們只需要將每個輪播頁封裝成一個僅包含一個UIImageView的ViewController,並將其設為UIPageViewController的ViewControllers,並實現它的代理方法就很容易實現我們的輪播圖。

UIScrollView提供了最基本的滾動封裝。采用UIScrollView作為基類,需要自己根據圖片的數量計算ContentSize,並在ScrollView相對應的代理方法中,根據Frame相關信息來計算頁碼,會比UIPageViewController稍微復雜。但好在,它是一個View。

UICollectionView做為UIScrollView的子類提供了比UIScrollView更好的封裝,也提供了"翻頁"的接口,並提供了一系列定位CollectionView的狀態信息。相比UIScrollView,他提供了更深層的封裝。同UIPageViewController一樣,非常適合本作為本組件的基類。同時,當UICollectionView的一些代理方法不足以提供相關信息時,還可以通過UIScrollView的代理方法來解決。

采用UIPageViewController作為子類,考慮到使用者需要將其作為subViewController添加到它們的項目中,沒有將一個View添加到ViewController中來得直觀,綜合考慮,PSCarouselView選擇了UICollectionView作為其基類。

實現功能

如何利用UICollectionView在有限的圖片數量下實現無限輪播?我們只需要N+2個Cell就可以實現無限輪播。如圖:

QQ截圖20151116151316.png

將imageURL數組中的URL,擴充為N+2。同時,將imageURLs的最後一個URL作為新數組的第一個URL,將imageURLs的第一個URL作為新數組的最後一個URL。然後在我們的CollectionView滾動到最後一個Cell時,跳轉到第二個URL表示的IndexPath即可。自動輪播采用計時器來完成。在實現的時候,我們需要注意一下的幾點

1.計時器的開啟與暫停

自動輪播,通常我們會使用到計時器NSTimer。當我們在頁面切換的過程中,需要注意計時器的開啟與暫停,不然可能會出現一些不可預料的BUG。而一個ViewController的生命周期,View是不可能知道的。因此我們需要提供兩個接口,供使用者開啟/暫停計時器。

- (void)startMoving;
- (void)stopMoving;

另外,我們還需要注意App的生命周期,當App進入不活動的狀態時,我們也需要將計時器暫停,並在回到活動狀態時相應地啟動。

#pragma mark - Notification
//程序被暫停的時候,應該停止計時器
- (void)applicationWillResignActive
{
    [self stopMoving];
}

//程序從暫停狀態回歸的時候,重新啟動計時器
- (void)applicationDidBecomeActive
{
    if (self.isAutoMoving)
    {
        [self startMoving];
    }
}

2.用戶操作與自動輪播的沖突 

用戶操作時如果輪播還是繼續滾動,會導致用戶產生不可控的錯覺。《iOS Human Interface Guide》中強調過App需要給於用戶控制感。因此,在用戶手動"翻頁"的時候,我們需要暫時暫停一下自動輪播,並在用戶手動"翻頁"完成後,重新進行輪播。

//用戶手動拖拽,暫停一下自動輪播
- (void)scrollViewWillBeginDragging:(UIScrollView     *)scrollView
{
    [self removeTimer];
}

//用戶拖拽完成,恢復自動輪播(如果需要的話)
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    if (self.isAutoMoving)
    {
        [self addTimer];
    }
}

3.圖片的緩存與展示 

如何通過圖片的URL來獲得相對應的圖片,並做好圖片的緩存,以實現最大限度地節省流量?這個模塊其實應該單獨做一個有關圖片緩存的庫,而不應該包含到View層中來。本作采用了SDWebImage來實現這一功能。有興趣的朋友可以嘗試著實現一個圖片緩存庫。

以上,一個輪播組件的封裝基本上完成了。更多實現上的細節請參看GitHub上的Demo。

PSCarouselView.gif

That's all.Hope you enjoy it!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved