1. 程式人生 > >iOS專案模仿之喜馬拉雅(三)—— 分段選擇器實現

iOS專案模仿之喜馬拉雅(三)—— 分段選擇器實現

 封裝控制元件在iOS開發中是常遇到的事,如果專案比較趕的話,我們可以用別人寫好的開源專案,但是對於技術提升來說,最好還是自己封裝,這是一個app的模仿,我們的目的就是要提高技術水平,所以嘗試封裝一下。記錄自己的思路,有時間對比一下別人的思路,可以收穫更多,當然自己思考在前,免得受到別人的影響。下面就這個專案而言,我們封裝一下分段選擇欄。 基本看一下app就會發現分段選擇欄在多處被用到了。分析一下它們的特點,找出共性,這樣方便設計出可複用的元件。我們來列一下它們的共同點: 1)和TabBar一樣,每個小按鈕都是可選擇的,並且有選擇效果。 2)佈局上是等分佈局。 3)選擇時下面的線有滑動動畫。 4)除了點選子專案外,滑動下面的View也可以切換選擇。 5)點選後除了自身的點選效果,還可以新增處理事件。 然後我們看一下它們的不同點,這樣方便我們設計介面時確定引數。不同點如下: 1)標題和標題的個數不同。 2)下滑線的長度不同。 3)字型大小(提高擴充套件效能,我們讓顏色也是可選擇的) 4)再多觀察一下,可能不全部是等分佈局。子專案太多的時候,標題長度是不一樣,而且可能會超出螢幕。 到這裡我們可以給出幾個初步設計方案了。 1)直接封裝一個UIView的子類,在UIView上新增子專案,能夠處理點選時間,子專案選擇UIButton。 2)考慮到超出螢幕的時候需要滑動,所以選擇UIScrollView可能更好。 3)但是再考慮一下複用的問題,我們可不可以嘗試一下UICollectionView。 當然上面的思路也只是一個初步的設計。 經過仔細考慮,設計類FDSegment,設計過程: 1)對基本UI元素的設計,我們選擇繼承UIScrollView,這樣可以實現滑動效果,而且佈局上並不複雜,選擇用UICollectionView有點浪費。 2)佈局不用自動佈局這種方式,因為我們希望封裝的空間能夠直接提取出來,所以要儘量不去依賴其他開源專案(自動佈局的話Masonry比較好用),而且自動佈局效率比較低。對於本專案中的分段選擇欄的實現,佈局上照考慮的是Item的寬度問題。由於不是所有情況都是等分的,所有我們需要設計一個設定Item寬度的介面,這種情況和UITableView設定cell的高度的情況極為類似,所以我們參考UITableView的設計,設計一個dataSource的代理。同樣我們可以將titles做為資料來源,放在代理中。
3) 其程式碼如下: ////  FDSegment.h//  Himalayan////  Created by fdd_zhangou on 16/3/7.//  Copyright © 2016 fdd_zhangou. All rights reserved.//#import <UIKit/UIKit.h>@classFDSegment;
@protocol  FDSegmentDataSource<NSObject>

- (
NSArray *)titlesForSegment:(FDSegment *)segment;
- (
CGFloat)segment:(FDSegment *)segment widthForItemAtIndex:(
NSInteger)index;
- (
CGFloat)segment:(FDSegment *)segment widthForIndicatorAtIndex:(NSInteger)index;

@end@protocol  FDSegmentDelegate<NSObject>

- (
void)segment:(FDSegment *)segment didSelectedItemAtIndex:(NSUInteger)index;

@end@protocolFDSegmentDataSource;
@protocolFDSegmentDelegate;

@interface FDSegment :
UIScrollView@property (nonatomic) NSUInteger seletedIndex;
@property (nonatomic) CGFloat heightForIndicator;

@property (nonatomic, weak)   id<FDSegmentDataSource> dataSource;
@property (nonatomic, weak)   id<FDSegmentDelegate> delegate;

@property (nonatomic, strong) UIFont *font;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) UIColor *selectedColor;

@property (nonatomic, strong) UITableView *tableView;

- (void)reloadData; @end //
//  FDSegment.m
//  Himalayan
//
//  Created by fdd_zhangou on 16/3/7.
//  Copyright © 2016 fdd_zhangou. All rights reserved.
//

#import "FDSegment.h"
#import
"NSString+Extension.h"
@interface FDSegment ()

@property (nonatomic, strong) NSMutableArray *titles;
@property (nonatomic, strong) NSMutableArray *items;

@property (nonatomic, strong) UIView *indicator;

@property (nonatomic) CGFloat height;

@end

@implementation FDSegment

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.contentSize = frame.size;
        self.showsHorizontalScrollIndicator = NO;
        self.showsVerticalScrollIndicator = NO;
    }
    return self;
}

- (void)layoutSubviews
{
    if (self.titles){
        CGFloat x = 0;
        for (int i = 0; i < self.titles.count; i++)
        {
            UIButton *item = [self itemAtIndex:i];
            if (!item.superview )
            {
                [self addSubview:item];
            }
            item.frame = CGRectMake(x, 0, [self widthForItemAtIndex:i], self.height - self.heightForIndicator);
            x += item.frame.size.width;
        }
        self.contentSize = CGSizeMake(x, self.frame.size.height);
       
        [self addSubview:self.indicator];
        UIView *selectedItem = [self itemAtIndex:self.seletedIndex];
        CGFloat centerX = selectedItem.center.x;
        if (!self.indicator.superview)
        {
            [self addSubview:self.indicator];
        }
        self.indicator.frame = CGRectMake(centerX - [self widthForIndicatorAtIndex:self.seletedIndex]/2 , self.height - self.heightForIndicator, [self widthForIndicatorAtIndex:self.seletedIndex] ,self.heightForIndicator);
    }
}

- (CGFloat)widthForItemAtIndex:(NSUInteger)index
{
    if (self.dataSource && [self.dataSource respondsToSelector:@selector(segment:widthForItemAtIndex:)])
    {
        return [self.dataSource segment:self widthForItemAtIndex:index];
    }
   
    return self.frame.size.width/self.titles.count;
}

- (CGFloat)widthForIndicatorAtIndex:(NSUInteger)index
{
    if (self.dataSource && [self.dataSource respondsToSelector:@selector(segment:widthForIndicatorAtIndex:)])
    {
        return [self.dataSource segment:self widthForIndicatorAtIndex:index];
    }
    UIButton *item = [self.items objectAtIndex:index];
    NSString *title = [self.titles objectAtIndex:index];
    UIFont *font = [item.titleLabel font];
    return [self string:title sizeWithFont:font maxSize:CGSizeMake(MAXFLOAT, MAXFLOAT)].width + 2;
}

- (CGFloat)heightForIndicator
{
    if (_heightForIndicator > 0) {
        return _heightForIndicator;
    }
    return 2;
}

- (void)setSeletedIndex:(NSUInteger)seletedIndex
{
    _seletedIndex = seletedIndex;

    for (int i = 0; i < self.titles.count; i++) {
       
        UIButton *item = [self itemAtIndex:i];
        if (_seletedIndex == item.tag)
        {
            item.selected = YES;
        }
        else
        {
            item.selected = NO;
        }
    }
   
    [UIView animateWithDuration:0.1 animations:^{
       
        UIView *selectedItem = [self itemAtIndex:_seletedIndex];
        CGFloat centerX = selectedItem.center.x;
        if (!self.indicator.superview)
        {
            [self addSubview:self.indicator];
        }
        self.indicator.frame = CGRectMake(centerX - [self widthForIndicatorAtIndex:_seletedIndex]/2 , self.height - self.heightForIndicator, [self widthForIndicatorAtIndex:_seletedIndex] ,self.heightForIndicator);
       
    }];
   
    if (self.delegate && [self.delegate respondsToSelector:@selector(segment:didSelectedItemAtIndex:)])
    {
        [self.delegate segment:self didSelectedItemAtIndex:seletedIndex];
    }
}



#pragma -mark

- (NSMutableArray *)items
{
    if (!_items) {
        _items = [[NSMutableArray alloc] init];
    }
    return _items;
}

- (NSMutableArray *)titles
{
    if (!_titles) {
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(titlesForSegment:)])
        {
            _titles = [[self.dataSource titlesForSegment:self] mutableCopy];
        }
        else
        {
            NSLog(@"must set titles for segment");
        }
    }
    return _titles;
}

- (UIView *)indicator
{
    if (!_indicator) {
        _indicator = [[UIView alloc] init];
        _indicator.backgroundColor = [UIColor redColor];//預設顏色
    }
    return _indicator;
}

- (UIButton *)itemAtIndex:(NSUInteger)index
{
    if (index >= self.items.count) {
        UIButton *item = [[UIButton alloc] init];
        [item setTitle:[self.titles objectAtIndex:index] forState:UIControlStateNormal];
        [item setTitle:[self.titles objectAtIndex:index] forState:UIControlStateSelected];
        [item setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
        [item setTitleColor:self.selectedColor forState:UIControlStateSelected];
        [item addTarget:self action:@selector(selectedItem:) forControlEvents:UIControlEventTouchUpInside];
        item.tag = index;
        [self.items addObject:item];
    }
    return  [self.items objectAtIndex:index];

}

- (void)selectedItem:(UIButton *)item
{
    self.seletedIndex = item.tag;
}
- (void)reloadData
{
    self.titles = nil;
}

- (UIColor *)selectedColor
{
    if (!_selectedColor) {
        _selectedColor = [UIColor redColor];
    }
    return _selectedColor;
}

- (CGFloat)height
{
    return self.frame.size.height;
}

-(CGSize)string:(NSString *)string sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize
{
    NSDictionary *attrs = @{NSFontAttributeName : font};
    return [string boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
}

@end
效果如下: 上面兩種情況,分別對應開始我們看到的幾個頁面中的分段選擇欄的情況。是基本滿足目前的要求的,關於超出螢幕的內容,需要滑動效果的,也基本實現了,但是可能有些小瑕疵,我們後面具體遇到了再解決。