分析WyhPageControl,談?wù)刄I組件的封裝思想

WyhPageControl

A customizable pageControl,support many styles including custom image、tintcolor ,etc

Github

https://github.com/XiaoWuTongZhi/WyhPageControl

CocoaPods Support

pod search WyhPageControl
pod 'WyhPageControl', '~> 1.0.0'

前段時(shí)間項(xiàng)目整體UI及交互大改,需要準(zhǔn)備封裝很多UI組件,借著這個(gè)機(jī)會(huì)封裝了不少實(shí)用組件,這里給大家介紹一款支持自定義的pageControl吧(其實(shí)我就是來騙star的,github上很少有人去封裝pageControl,有一個(gè)上千star的pageControl也已經(jīng)好多年沒維護(hù)了,因?yàn)樘?jiǎn)單了,但是今天介紹的不僅僅是一個(gè)第三方組件,更多的教大家如何去封裝一個(gè)自定義UI的方式方法)。

傳統(tǒng)意義的UIPageControl想必根本滿足不了廣大開發(fā)同胞的使用了,項(xiàng)目越來越遠(yuǎn)離Native頁面,也著實(shí)讓我們很頭疼!

PageControl封裝起來很簡(jiǎn)單,因?yàn)樗墓δ芤埠芎?jiǎn)單,無非是多一些系統(tǒng)沒有的自定義小點(diǎn)點(diǎn)的功能罷了,不過封裝思想很重要。從工作至今,封裝過很多組件,其實(shí)思想都大同小異,下面我們來分析一波代碼?。ㄇ煤诎辶藠W)

封裝思想

這里僅談?wù)?code>UI組件的封裝,日后我還會(huì)出一些針對(duì)業(yè)務(wù)模塊的封裝思想。

UI組件封裝都大同小異,像Native原生tableView就是一個(gè)很好的例子,支持自定義最大化的組件往往并不是暴露很多的自定義屬性,而是直接用代理回調(diào)的方式,讓使用者去自定義這個(gè)組件的樣式,而不是已定的樣式。這一點(diǎn)是很重要的,你要清楚你所暴露的自定義屬性永遠(yuǎn)沒有辦法滿足所有人的需求,因此代理回調(diào)很重要。讓我們通過分析WyhPageControl來理解如何通過代理回調(diào)來自定義UI組件:

WyhPageControl代碼分析

WyhPageControl設(shè)計(jì)之初就是想自定義pageControl的小圓點(diǎn)樣式、間距、圓點(diǎn)尺寸等,那么完全可以仿照tableView的代理模式,將小圓點(diǎn)作為cell,通過代理回調(diào)的方式,讓用戶去自定義,當(dāng)然還是要暴露一些自定義屬性的,最好維持UIPageControl的屬性不變,起碼使用起來更舒服。

WyhPageControl作為主體View,WyhPageControlDot作為cell,內(nèi)部實(shí)現(xiàn)一點(diǎn)要分清楚哪些是支持reload的方法:

初始化方法,采用block回調(diào)自定義配置使代碼塊更聚合,block內(nèi)無需考慮循環(huán)引用,dataSourcedelegate一定要分清,參考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;
}

創(chuàng)建協(xié)議代理,來高度自定義你的dot,dataSourcedelegate一定要分清并分開,結(jié)構(gòu)一定要嚴(yán)格規(guī)范,為了可拓展性和便于維護(hù):

@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

初始化一些屬性,確保所有屬性都有默認(rèn)值。

- (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的時(shí)候會(huì)發(fā)生樣式的變化。

#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指定的樣式來創(chuàng)建dot,visibleDots是用來存放所有dot的數(shù)組,這里一定要判斷代理人是否給回調(diào)了dot,如果沒有自定創(chuàng)建一個(gè)默認(rèn)的dot,并通過用戶設(shè)置的間距等屬性,設(shè)置dot的位置,并添加點(diǎn)擊手勢(shì)。(這里要注意的是,這個(gè)initDots方法一定是一個(gè)支持reload的,使用者可能會(huì)根據(jù)不同情況返回不同的dot,這點(diǎn)必須清楚)

- (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.
}

增加與之對(duì)應(yīng)的reload方法拋給用戶,同tableView一樣,這個(gè)reload方法執(zhí)行后,會(huì)重新調(diào)用所有代理回調(diào)及自定義屬性,保證更新機(jī)制,這也是整個(gè)UI自定義組件封裝最重要的環(huán)節(jié)?。╓yhPageControl增加了一個(gè)轉(zhuǎn)圈圈的菊花,用戶可以定義顯示與隱藏當(dāng)在reload時(shí))

- (void)reloadData {
    
    [self reloadUI];
}

- (void)reloadUI {
    
    [self showActivity:YES];
    [self checkHiddenIfNeeded];
    [self configPageControlUI];
    [self initDots];
    [self showActivity:NO];
}

最后就是當(dāng)點(diǎn)擊dot時(shí),回調(diào)代理方法:

- (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];
    }
}

每一個(gè)dot有兩種狀態(tài)對(duì)應(yīng)兩種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一樣,你也可以自定義繼承這個(gè)dot的Base類,來自定義你的圓點(diǎn),這里就不舉例子了。

使用方法請(qǐng)大家去demo中自行查看,很簡(jiǎn)單,同tableView類似。

總結(jié)

通過分析這個(gè)簡(jiǎn)單的組件,希望朋友們對(duì)于UI組件封裝思想能更加理解,最后希望喜歡的朋友們到GitHub幫點(diǎn)個(gè)star,歡迎各種好朋友,一起來探討、研究,接下來我會(huì)出一些其他方面的,不只是UI層次的,簡(jiǎn)書這個(gè)平臺(tái)挺好,(但就是有時(shí)太懶),大家共勉吧。

開啟傳送門:WyhPageControl

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容