iOS專案模仿之喜馬拉雅(三)—— 分段選擇器實現
阿新 • • 發佈:2019-01-06
封裝控制元件在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 效果如下: 上面兩種情況,分別對應開始我們看到的幾個頁面中的分段選擇欄的情況。是基本滿足目前的要求的,關於超出螢幕的內容,需要滑動效果的,也基本實現了,但是可能有些小瑕疵,我們後面具體遇到了再解決。
@protocol FDSegmentDataSource<NSObject>
- (NSArray *)titlesForSegment:(FDSegment *)segment;
- (CGFloat)segment:(FDSegment *)segment widthForItemAtIndex:(
- (CGFloat)segment:(FDSegment *)segment widthForIndicatorAtIndex:(NSInteger)index;
@end@protocol FDSegmentDelegate<NSObject>
- (void)segment:(FDSegment *)segment didSelectedItemAtIndex:(NSUInteger)index;
@end@protocolFDSegmentDataSource;
@protocolFDSegmentDelegate;
@interface FDSegment :
@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 效果如下: 上面兩種情況,分別對應開始我們看到的幾個頁面中的分段選擇欄的情況。是基本滿足目前的要求的,關於超出螢幕的內容,需要滑動效果的,也基本實現了,但是可能有些小瑕疵,我們後面具體遇到了再解決。