iOS開發之控制元件封裝(又名擰螺絲):排序按鈕

iu
前言
排序按鈕是實際開發中比較常見的一種控制元件,最近我也遇到了,鑑於只會擰螺絲的我有一段時間沒寫關於擰螺絲的文章了,為了找下存在感,今天簡單分享下。
雖然功能簡單,但是保證你看了不虧,尤其是對UI這塊比較薄弱的同學來說。

手把手教你擰螺絲
OK,先看圖:

4個排序按鈕.gif
簡單描述一下:
按鈕一共有三種狀態:非選中、選中升序、選中降序。

按鈕的三種狀態
點選按鈕時有兩種情況:
- 按鈕原本處於非選中狀態,點選,切換到選中狀態,其狀態變為升序。
- 按鈕原本就處於選中狀態,再點選一下,則切換其排序狀態(升變降、降變升)。
不同狀態對應不同的icon,如果沒有UI,可以去 ofollow,noindex"> iconfont 找圖示,輸入關鍵詞如“上下箭頭”就可以找到你需要的icon。
基本思路
繼承 UIButton
,直接在 button
上放 view
,設定約束,根據按鈕的狀態設定對應的圖片。
PS:自定義按鈕最靈活的做法就是直接在button上放view(在不需要糾結記憶體和view層級的情況下),簡單粗暴、隨心所欲。
完整程式碼
.h檔案:
#import <UIKit/UIKit.h> @interface CQSortButton : UIButton /** 按鈕文字 */ @property (nonatomic, copy) NSString *title; /** 是否是升序 */ @property (nonatomic, assign, readonly, getter=isAscending) BOOL ascending; @end
.m檔案:
#import "CQSortButton.h" @interface CQSortButton () /** 文字label */ @property (nonatomic, strong) UILabel *cq_titleLabel; /** 箭頭imageView */ @property (nonatomic, strong) UIImageView *cq_arrowImageView; @end @implementation CQSortButton #pragma mark - 構造方法 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setupUI]; } return self; } #pragma mark - UI搭建 - (void)setupUI { self.layer.borderColor = [UIColor blackColor].CGColor; self.layer.borderWidth = 1; // 文字和圖片的父view UIView *contentView = [[UIView alloc] init]; [self addSubview:contentView]; contentView.userInteractionEnabled = NO; [contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.centerX.mas_equalTo(self); make.left.mas_greaterThanOrEqualTo(self).mas_offset(3); make.right.mas_lessThanOrEqualTo(self).mas_offset(-3); }]; // 文字 self.cq_titleLabel = [[UILabel alloc] init]; [contentView addSubview:self.cq_titleLabel]; self.cq_titleLabel.font = [UIFont boldSystemFontOfSize:13]; self.cq_titleLabel.adjustsFontSizeToFitWidth = YES; [self.cq_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.left.mas_offset(0); }]; // 圖片 self.cq_arrowImageView = [[UIImageView alloc] init]; [contentView addSubview:self.cq_arrowImageView]; self.cq_arrowImageView.image = [UIImage imageNamed:@"up_down"]; [self.cq_arrowImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(20, 20)); make.centerY.mas_equalTo(contentView); make.left.mas_equalTo(self.cq_titleLabel.mas_right); make.right.mas_equalTo(contentView); }]; } #pragma mark - 賦值選中狀態 - (void)setSelected:(BOOL)selected { //// 注意: //// selected 表示你要賦值的狀態 //// super.selected 表示當前處於的狀態 if (selected) { // 即將設定成選中狀態 if (super.selected) { // 如果原本就處於選中狀態 // 那麼就切換篩選狀態 _ascending = !_ascending; if (_ascending) { // 升序 self.cq_arrowImageView.image = [UIImage imageNamed:@"red_arrow_up"]; } else { // 降序 self.cq_arrowImageView.image = [UIImage imageNamed:@"red_arrow_down"]; } } else { // 如果之前不是選中狀態 // 那麼設定成選中的預設排序狀態:升序 _ascending = YES; self.cq_arrowImageView.image = [UIImage imageNamed:@"red_arrow_up"]; } } else { // 即將設定成非選中狀態 // 設定成非選中狀態的圖片 self.cq_arrowImageView.image = [UIImage imageNamed:@"up_down"]; } // 最後再賦值 [super setSelected:selected]; } #pragma mark - 賦值文字 - (void)setTitle:(NSString *)title { _title = title; self.cq_titleLabel.text = title; } @end
使用:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSArray *titleArray = @[@"同比", @"銷售額", @"", @"文字有點多啊"]; NSMutableArray *buttonArray = [NSMutableArray array]; for (int i = 0; i < 4; i++) { CQSortButton *button = [[CQSortButton alloc] init]; [self.view addSubview:button]; button.title = titleArray[i]; button.tag = CQSortButtonBeginTag + i; [button addTarget:self action:@selector(sortButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; [buttonArray addObject:button]; } // 按鈕等寬依次排列 [buttonArray mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:0 leadSpacing:0 tailSpacing:0]; [buttonArray mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(100); make.height.mas_equalTo(40); }]; } - (void)sortButtonClicked:(CQSortButton *)sender { for (int i = 0; i < 4; i++) { CQSortButton *button = [self.view viewWithTag:(CQSortButtonBeginTag + i)]; button.selected = (button.tag == sender.tag); } NSLog(@"第%ld個按鈕點選,狀態:%@", (long)(sender.tag-CQSortButtonBeginTag), sender.isAscending ? @"升序" : @"降序"); }
知識點及細節
1.如何讓兩個view整體居中並且不超出父view?
建立父view是關鍵。
先建立一個父view,這個父view居中於button, 左右不設定固定約束 ,再將兩個view放在父view上,左邊的view與父view左對齊,右邊的view與父view右對齊,左邊的view與右邊的view水平方向約束確定,撐開父view:
// 文字和圖片的父view UIView *contentView = [[UIView alloc] init]; [self addSubview:contentView]; [contentView addSubview:self.cq_titleLabel]; [contentView addSubview:self.cq_arrowImageView]; [self.cq_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.left.mas_offset(0); }]; [self.cq_arrowImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(20, 20)); make.centerY.mas_equalTo(contentView); make.left.mas_equalTo(self.cq_titleLabel.mas_right); make.right.mas_equalTo(contentView); }];
不超出父view(此處指button)用 mas_greaterThanOrEqualTo
和 mas_lessThanOrEqualTo
即可:
[contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.centerX.mas_equalTo(self); make.left.mas_greaterThanOrEqualTo(self).mas_offset(3); make.right.mas_lessThanOrEqualTo(self).mas_offset(-3); }];
2.readonly的使用
/** 是否是升序 */ @property (nonatomic, assign, readonly, getter=isAscending) BOOL ascending;
為什麼這裡要用readonly?
因為這個屬性的改變只能是通過內部(自身的.m)改變,而不能通過外部改變,或者說這個屬性 只是用來反映按鈕的一個狀態 ,就像 UIScrollView
的 decelerating
屬性一樣,只是反映scrollView正在減速,不能通過呼叫 scrollView.decelerating = YES
讓它主動減速。
// returns YES if user isn't dragging (touch up) but scroll view is still moving @property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating;
總結一下就是readonly適用於 只用來反映物件的狀態、特徵或特性 的屬性。
你可以找幾個蘋果官方文件裡的readonly屬性好好感受一下。

3.如何使用masonry等寬等間距排列控制元件?

用masory提供的 mas_distributeViewsAlongAxis
方法:
// 按鈕等寬依次排列 [buttonArray mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:0 leadSpacing:0 tailSpacing:0]; [buttonArray mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(100); make.height.mas_equalTo(40); }];
關於這個方法的更多使用可以參考這篇文章:
iOS Masonry 等間隔或等寬高排列多個控制元件 ,很實用的技能,建議熟練掌握。
需要注意的是呼叫 mas_distributeViewsAlongAxis
這個方法的陣列其元素個數必須 bigger than one
,否則沒有效果,masonry原始碼擷取:
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing { if (self.count < 2) { NSAssert(self.count>1,@"views to distribute need to bigger than one"); return; } ...... }
所以實際開發中如果你是獲取後臺的陣列來展示的話,務必先判斷陣列的count。
demo
https://github.com/CaiWanFeng/iOS_Storage
demo位置:

反思
工作兩年多,只會擰螺絲,現在的我,慌的一批。。。
所以:
