分析WyhPageControl,談談UI元件的封裝思想
WyhPageControl
A customizable pageControl,support many styles including custom image、tintcolor ,etc

Github
ofollow,noindex">https://github.com/XiaoWuTongZhi/WyhPageControl
CocoaPods Support
pod search WyhPageControlpod 'WyhPageControl', '~> 1.0.0'
前端時間專案整體UI及互動大改,需要準備封裝很多UI元件,藉著這個機會封裝了不少實用元件,這裡給大家介紹一款支援自定義的 pageControl
吧(其實我就是來騙star的,github上很少有人去封裝pageControl,有一個上千star的pageControl也已經好多年沒維護了,因為太簡單了, 但是今天介紹的不僅僅是一個第三方元件,更多的教大家如何去封裝一個自定義UI的方式方法 )。
傳統意義的 UIPageControl
想必根本滿足不了廣大開發同胞的使用了,專案越來越遠離 Native
頁面,也著實讓我們很頭疼!
PageControl
封裝起來很簡單,因為它的功能也很簡單,無非是多一些系統沒有的自定義小點點的功能罷了,不過封裝思想很重要。從工作至今,封裝過很多元件,其實思想都大同小異,下面我們來分析一波程式碼!(敲黑板了奧)
封裝思想
這裡僅談談 UI元件
的封裝,日後我還會出一些針對業務模組的封裝思想。
UI元件封裝都大同小異,像 Native原生
的 tableView
就是一個很好的例子, 支援自定義最大化的元件往往並不是暴露很多的自定義屬性,而是直接用代理回撥的方式,讓使用者去自定義這個元件的樣式,而不是已定的樣式。 這一點是很重要的,你要清楚你所暴露的自定義屬性永遠沒有辦法滿足所有人的需求,因此代理回撥很重要。讓我們通過分析 WyhPageControl
來理解如何通過代理回撥來自定義UI元件:
WyhPageControl程式碼分析
WyhPageControl
設計之初就是想自定義pageControl的小圓點樣式、間距、圓點尺寸等,那麼完全可以仿照 tableView
的代理模式,將小圓點作為 cell
,通過代理回撥的方式,讓使用者去自定義,當然還是要暴露一些自定義屬性的,最好維持 UIPageControl
的屬性不變,起碼使用起來更舒服。
WyhPageControl
作為主體View, WyhPageControlDot
作為cell,內部實現一點要分清楚哪些是支援 reload
的方法:
初始化方法,採用block回撥自定義配置使程式碼塊更聚合,block內無需考慮迴圈引用, dataSource
和 delegate
一定要分清,參考 UITableView
。
- (instancetype)initWithDataSource:(id<WyhPageControlDataSource>)dataSource Delegate:(id<WyhPageControlDelegate>)delegate WithConfiguration:(void (^)(WyhPageControl *))configuration { if (self = [self init]) { if(configuration) configuration(self); _dataSource = dataSource; _delegate = delegate; [self reloadUI]; } return self; }
建立協議代理,來高度自定義你的 dot
, dataSource
和 delegate
一定要分清並分開,結構一定要嚴格規範,為了可拓展性和便於維護:
@class WyhPageControl; @protocol WyhPageControlDataSource <NSObject> @optional - (WyhPageControlDot *)pageControl:(WyhPageControl *)pageControl dotForIndex:(NSInteger)index; @end @protocol WyhPageControlDelegate <NSObject> @optional - (void)pageControl:(WyhPageControl *)pageControl didClickForIndex:(NSInteger)index; @end
初始化一些屬性,確保所有屬性都有預設值。
- (void)initializeConfig { self.clipsToBounds = YES; _numberOfPages = 0; _currentPage = 0; _hidesForSinglePage = NO; _pageIndicatorTintColor = [UIColor lightGrayColor]; _currentPageIndicatorTintColor = [UIColor darkGrayColor]; _backgroundColor = [UIColor clearColor]; _borderWidth = 0.f; _cornerRadius = 0.f; _borderColor = [UIColor darkGrayColor]; _backgroundImage = nil; _showReloadActivityIndicator = YES; // const _dotLeftMargin = 15.f; _dotTopMargin = 8.f; _dotSpace = 8.f; // ui _coverView = [[UIView alloc]init]; _coverImageView = [[UIImageView alloc]init]; _indicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyleGray)]; [_indicator sizeToFit]; [self addSubview:_coverView]; [self addSubview:_coverImageView]; [self addSubview:_indicator]; }
並重寫這些屬性的 setter
方法,使其重新set的時候會發生樣式的變化。
#pragma mark - Setter - (void)setNumberOfPages:(NSUInteger)numberOfPages { _numberOfPages = numberOfPages; } - (void)setHidesForSinglePage:(BOOL)hidesForSinglePage { _hidesForSinglePage = hidesForSinglePage; } - (void)setBackgroundColor:(UIColor *)backgroundColor { _backgroundColor = backgroundColor; _coverView.backgroundColor = backgroundColor; } - (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor { _pageIndicatorTintColor = pageIndicatorTintColor; } - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor { _currentPageIndicatorTintColor = currentPageIndicatorTintColor; } - (void)setDotLeftMargin:(CGFloat)dotLeftMargin { _dotLeftMargin = dotLeftMargin; } - (void)setDotTopMargin:(CGFloat)dotTopMargin { _dotTopMargin = dotTopMargin; } - (void)setDotSpace:(CGFloat)dotSpace { _dotSpace = dotSpace; } - (void)setBorderWidth:(CGFloat)borderWidth { _borderWidth = borderWidth; self.layer.borderWidth = borderWidth; } - (void)setBorderColor:(UIColor *)borderColor { _borderColor = borderColor; self.layer.borderColor = borderColor.CGColor; } - (void)setBackgroundImage:(UIImage *)backgroundImage { _backgroundImage = backgroundImage; _coverImageView.image = backgroundImage; } - (void)setCornerRadius:(CGFloat)cornerRadius { _cornerRadius = cornerRadius; self.layer.cornerRadius = cornerRadius; }
通過 dataSource
指定的樣式來建立 dot
, visibleDots
是用來存放所有 dot
的陣列,這裡一定要判斷代理人是否給回調了 dot
,如果沒有自定建立一個預設的 dot
,並通過使用者設定的間距等屬性,設定 dot
的位置,並新增點選手勢。(這裡要注意的是,這個 initDots 方法一定是一個支援 reload 的,使用者可能會根據不同情況返回不同的 dot
,這點必須清楚)
- (void)initDots { [self.visibleDots makeObjectsPerformSelector:@selector(removeFromSuperview)]; self.visibleDots = [NSMutableArray new]; WyhPageControlDot *lastDot ; for (int i = 0; i < _numberOfPages; i++) { WyhPageControlDot *dot = nil; if (![self.dataSource respondsToSelector:@selector(pageControl:dotForIndex:)]) { dot = [[WyhPageControlDot alloc]init]; dot.unSelectTintColor = _pageIndicatorTintColor; dot.selectTintColor = _currentPageIndicatorTintColor; }else { dot = [self.dataSource pageControl:self dotForIndex:i]; } dot.hidden = _isReloading; // frame CGFloat dotX = (!lastDot)?_dotLeftMargin:CGRectGetMaxX(lastDot.frame)+_dotSpace; dot.frame = CGRectMake(dotX, 0, dot.size.width, dot.size.height); // gesture UITapGestureRecognizer *tapges = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(pageControlDotTapAction:)]; [dot addGestureRecognizer:tapges]; [self addSubview:dot]; [self.visibleDots addObject:dot]; lastDot = dot; } [self bringSubviewToFront:self.indicator]; [self configDotsUI]; [self autoConfigBounds]; [self configAllDotsCenterY]; // must after autoConfigBounds. }
增加與之對應的 reload
方法拋給使用者,同tableView一樣,這個 reload
方法執行後,會重新呼叫所有代理回撥及自定義屬性,保證更新機制,這也是整個UI自定義元件封裝最重要的環節!(WyhPageControl增加了一個轉圈圈的菊花,使用者可以定義顯示與隱藏當在reload時)
- (void)reloadData { [self reloadUI]; } - (void)reloadUI { [self showActivity:YES]; [self checkHiddenIfNeeded]; [self configPageControlUI]; [self initDots]; [self showActivity:NO]; }
最後就是當點選 dot
時,回撥代理方法:
- (void)pageControlDotTapAction:(UITapGestureRecognizer *)tapGes { WyhPageControlDot *dot = (WyhPageControlDot *)tapGes.view; NSInteger index = [self.visibleDots indexOfObject:dot]; if (index == NSNotFound) { NSAssert(NO, @"Can't found this tap dot !"); return; } [self moveToIndex:index]; // call back if ([self.delegate respondsToSelector:@selector(pageControl:didClickForIndex:)]) { [self.delegate pageControl:self didClickForIndex:index]; } }
每一個 dot
有兩種狀態對應兩種UI樣式,選中和未選中,目前僅支援自定義 選中/未選中 顏色、背景圖片。
@interface WyhPageControlDot : UIView @property(nonatomic, strong) UIColor *unSelectTintColor; @property(nonatomic, strong) UIColor *selectTintColor; @property (nonatomic, strong) UIImage *unselectImage; @property (nonatomic, strong) UIImage *selectImage; @property (nonatomic, assign) CGSize size; // default is (20,20) @property (nonatomic, strong) UIColor *borderColor; //defult is nil; @property (nonatomic, assign) CGFloat borderWidth; // default is 0.f; @property (nonatomic, assign) CGFloat conerRadius; //default is 10.f - (void)setSelected:(BOOL)selected; @end
dot
如果不滿足你的需求,同cell一樣,你也可以自定義繼承這個 dot
的Base類,來自定義你的圓點,這裡就不舉例子了。
使用方法請大家去 demo
中自行檢視,很簡單,同 tableView
類似。
總結
通過分析這個簡單的元件,希望朋友們對於UI元件封裝思想能更加理解,最後希望喜歡的朋友們到 GitHub
幫點個 star
,歡迎各種好朋友,一起來探討、研究,接下來我會出一些其他方面的,不只是UI層次的,簡書這個平臺挺好,(但就是有時太懶),大家共勉吧。
開啟傳送門: WyhPageControl