iOS 控件封装(又名拧螺丝)之排序按钮的开发

前言

排序按钮是实际开发中比较常见的一种控件,最近我也遇到了,今天简单分享下。

虽然功能简单,但是保证你看了不亏,尤其是对UI这块比较薄弱的同学来说。

OK,先看图:

简单描述一下:

按钮一共有三种状态:非选中、选中升序、选中降序。

按钮的三种状态

点击按钮时有两种情况:

  1. 按钮原本处于非选中状态,点击,切换到选中状态,其状态变为升序。
  2. 按钮原本就处于选中状态,再点击一下,则切换其排序状态(升变降、降变升)。

不同状态对应不同的icon,如果没有UI,可以去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地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • iOS实现通过按钮添加和删除控件的方法

    本文实例为大家分享了iOS通过按钮添加和删除控件,供大家参考,具体内容如下 想要达到的效果如下: 先讲一下这个demo主要部分,即通过按钮实现增删图标 分析: 1.每一个图标需要两个数据,即图片和描述用的字符串 ,所以创建一个Item类来封装从plist文件读取出来的数据: 1)plist文件如下: 2)Item类: .h文件 #import <Foundation/Foundation.h> @interface Item : NSObject //描述的字符串 @property(nona

  • iOS应用UI开发中的字体和按钮控件使用指南

    UILabel的使用 一.初始化 复制代码 代码如下: UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 40, 120, 44)];      [self.view addSubview:myLabel]; 二.设置文字 ①.设置默认文本 复制代码 代码如下: NSString *text = @"标签文本"; myLabel.text = text; 效果: ②.设置标签文本(此属性是iOS6.0之后才

  • 学习iOS开关按钮UISwitch控件

    开关按钮UISwitch 在ViewController.h里面 #import <UIKit/UIKit.h> @interface ViewController : UIViewController{ //定义一个开关控件 //作用可以进行状态的改变 //开,关:两种状态可以切换 //所有UIKit框架库中的控件均已UI开头 //苹果官方的控件都定义在UIKit框架库中 UISwitch * _mySwitch; } @property(retain,nonatomic) UISwitch

  • iOS 控件封装(又名拧螺丝)之排序按钮的开发

    前言 排序按钮是实际开发中比较常见的一种控件,最近我也遇到了,今天简单分享下. 虽然功能简单,但是保证你看了不亏,尤其是对UI这块比较薄弱的同学来说. OK,先看图: 简单描述一下: 按钮一共有三种状态:非选中.选中升序.选中降序. 按钮的三种状态 点击按钮时有两种情况: 按钮原本处于非选中状态,点击,切换到选中状态,其状态变为升序. 按钮原本就处于选中状态,再点击一下,则切换其排序状态(升变降.降变升). 不同状态对应不同的icon,如果没有UI,可以去iconfont 找图标,输入关键词如"

  • 深入理解IOS控件布局之Masonry布局框架

    前言: 回想起2013年做iOS开发的时候,那时候并没有采用手写布局代码的方式,而是采用xib文件来编写,如果使用纯代码方式是基于window的size(320,480)计算出一个相对位置进行布局,那个时候windows的size是固定不变的,随着iphone5的发布,windows的size(320,568)也发生了变化,而采用autoresizingMask的方式进行适配,到后来iphone 6之后windows size的宽度也随之变化,开始抛弃autoresizingMask改用auto

  • 使用iOS控件UICollectionView生成可拖动的桌面的实例

    一个App受欢迎的程度,一方面来源于它本身为用户提供便捷的功能,另一方面则来源于它的UI.UI是用户体验重要的组成部分,构成UI的的元素恰恰离不开那些看似独立的控件.在开发的过程中,大家对UITableView应该很熟悉吧!确实UITableView在处理数据显示方面有着很强大的功能,例如网红们使用的微博,微信社交软件的聊天界面等等,这种流式布局使用UITableView简直最合适不过了:但毕竟UITableView不是万能的,当需要显示横纵向的数据时它就显得捉襟见肘了,虽然这也难不倒我们程序猿

  • android控件封装 自己封装的dialog控件

    自定义dialog肯定是用的很多了但是感觉每次做都是很乱 单纯完成任务而已,现在封装了一下 以后用到直接copy 先上图: 主activity 复制代码 代码如下: package com.su.testcustomdialog; import com.su.testcustomdialog.MyDialog.Dialogcallback; import android.app.Activity; import android.os.Bundle; import android.view.Vie

  • Android信息界面编辑及组合控件的封装

    本文实例为大家分享了Android编辑信息界面,及组合控件的封装,供大家参考,具体内容如下 Github地址(完整Demo,欢迎下载) 效果图 attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ItemGroup"> <!--标题的文字--> <attr name=&quo

  • Angular封装表单控件及思想总结

    前言 前端框架的强大无疑给开发者省去了不少烦恼,又因比较完善的UI库支撑,让部分后端开发者能够省去大量样式设计的时间成本,纵然如此,业务的多变性是框架本身无法预料的,很多的控件功能在实际开发中总是不够完善和灵活,所以需要开发者结合业务需求进行再次封装这些UI控件/组件. 表单控件 常规组件只需要根据官方指引,写好数据传输的方式和订阅即可任意使用,表单控件有点特殊,按照常规方式写出来的组件使用在表单中,绑定ngModel或者formControlName,随之而来的是一个报错: RROR Erro

  • Android自定义滑动接听电话控件组实例

    本文根据组件开发思想,首先介绍android自定义控件,然后将自定义的控件封装为jar包.最为实现滑动接听电话控件组. 一.目录结构 二.运行效果 三.代码实现 首先,自定义一个类IncomingPhone继承RelativeLayout public IncomingPhone(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; TextView textView = new Tex

  • 详解ASP.NET数据绑定操作中Repeater控件的用法

    一.绑定控件之Repeater .NET封装了多种数据绑定控件,诸如GridView.DataList等但该篇文章将会从Repeater入手,因为Repeater只提供了基本的数据绑定模板,没有内置其它分页等功能,所以它是最原始的数据绑定控件,只要能够熟练运用Repeater控件其它的绑定控件也就很简单了. 1.Repeater简介 Repeater 控件是基本模板化数据列表. 它不像GridView控件一样能够可视化的设计格式或样式,因此开发时在控件模板中必须显式声明所有格式.格式和样式标记.

  • Android开发之基本控件和四种布局方式详解

    Android中的控件的使用方式和iOS中控件的使用方式基本相同,都是事件驱动.给控件添加事件也有接口回调和委托代理的方式.今天这篇博客就总结一下Android中常用的基本控件以及布局方式.说到布局方式Android和iOS还是区别挺大的,在iOS中有Frame绝对布局和AutoLayout相对布局.而在Android中的布局方式就比较丰富了,今天博客中会介绍四种常用的布局方式.先总结一下控件,然后再搞一搞基本方式,开发环境还是用的Mac下的Android Studio.开始今天的正题, 虽然A

  • Ajax并不神秘:揭下各种Ajax控件和类库的小裤衩

    Ajax控件和类库现在真的太多了,不知不觉中增加了Ajax的神秘性和复杂性,看到版内很多人为此费解和伤神,决定发此贴谈谈本人对Ajax的观点,希望能让大家对Ajax有一个本质的认识. 观点一:Ajax和服务器端技术毫不相关 严格的说,与传统web开发相比,Ajax是完完全全的客户端技术.由于很多控件封装了客户端和服务器端的通信过程,因此很多问题也因通信而起.事实上,不论何种Ajax技术,服务器端都是返回的一个纯文本流,再由客户端来处理这个文本.这段文本可以是xml格式,也可以是一个Html片段,

随机推荐