App开发本质上是一种对人性的把握,我们在项目开发过程中经常需要自定义很多精美的控件,以此来获得更好的用户体验。
因此,掌握自定义控件是移动应用开发必备的一项技能。本篇文章我们就来快速实现自定义选项卡,在这个过程中同时也会讲解swift和OC的区别,以及特别需要注意的技巧,同时也涉及了oc调用swift的方法。
一、项目介绍
自定义选项卡实现的功能包括点击切换不同的选项,并且选中的字体颜色改变,且下方有指示器,示例如下。
二、项目思路
有一些开源的项目采用继承UIControl实现自定义选项卡, UIControl是UIButton,UISwitch,UItextField等控件的父类,当然这是最佳的方式。我们这里为了快速演示,尽量简单粗暴,采用自定义UIView。
具体实现思路如下:
1)在UIView添加指定个数的button,等宽等高。
2)每个button添加点击事件。
3)button的点击事件中修改选中和未选中的样式。
4)设置代理,根据button的tag处理相应的业务。
三、实战详解
1.OC项目调用swift类的准备工作
为了演示OC调用swift,我们在一个已有的OC工程中新建swift类。
1)在新建Swift类之前,进行相关设置,如下图:
在Build Settings中设置Product Module Name为当前工程名,设置Enable Modules为Yes。
配置完成后,系统会为工程创建一个“工程名-Swift.h”的文件,可以直接引用,不显示在Xcode中,在需要使用swift的OC类中需要引用该文件。
2)新建swift文件
填好新建的swift文件名后,会提示如下:
我们选择create,系统会建立“工程名-Bridging-Header.h”的桥接文件。
2.swift类编写
1)继承UIView并定义属性
class LWSegmentedControl: UIView {
var segmentDelegate: LWSegmentedDelegate?
//定义segment的button数组
var btnTitleSource: Array?
//定义未选中的字体颜色
var titleColor: UIColor?
//定义选中的字体颜色
var selectedColor: UIColor?
//定义选中的字体
var titleFont: UIFont?
//定义选中的指示器颜色
fileprivate var selectionIndicatorColor: UIColor?
//定义未选中的指示器颜色
fileprivate var normalIndicatorColor: UIColor?
//定义选中的index
fileprivate var selectedSegment: Int?
//定义按钮的宽度
fileprivate var witdthFloat: CGFloat?
//定义指示器的数组
fileprivate var indicatorSource: Array?
}
2)重写UIView的init方法
override init(frame: CGRect) {
super.init(frame: frame)
self.btnTitleSource = []
self.indicatorSource = []
selectedSegment = 0;
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
其中required init?(coder aDecoder: NSCoder) 是必不可少的,swift语言强制要求的。init方法中设置了UIView的frame大小,对按钮数组、指示器数组、默认选择项进行了初始化。
3)实现类函数
类函数的功能主要是外部传入参数,如颜色、title等,然后创建button数组、指示器数组、配置各种属性,以及设置代理。
//类方法
public class func build(_ frame: CGRect, titleDataSource: Array, backgroundColor: UIColor, titleColor: UIColor, titleFont: UIFont, selectColor: UIColor, normalIndicatorColor: UIColor, selectionIndicatorColor: UIColor, delegate:Any) ->LWSegmentedControl {
let customSegment: LWSegmentedControl = LWSegmentedControl.init(frame: frame)
customSegment.backgroundColor = backgroundColor
customSegment.normalIndicatorColor = normalIndicatorColor
customSegment.selectionIndicatorColor = selectionIndicatorColor
customSegment.titleColor = titleColor
customSegment.titleFont = titleFont
customSegment.selectedColor = selectColor
//设置代理
customSegment.segmentDelegate = delegate as? LWSegmentedDelegate
//添加button数组和指示器数组
customSegment.addSegmentArray(segmentArray: titleDataSource)
return customSegment
}
swift的类方法有两种写法,可以是class开头,也可以是static开头。这里的build参数较多,可以优化成单一属性,可以单独设置。为了便于理解,我们统一将参数传入。
我们看到delegate:Any,这个参数是Any类型。在Swift 3中,Objective-C中的 id
类型现在映射成了Swift中的 Any
类型,它可以代表任何类型的值,无论是类、枚举、结构体还是任何其他Swift类型。
代理的使用,我们在后续会详细讲解。
4)添加button数组和指示器数组
func addSegmentArray(segmentArray: Array) {
// 1.按钮的个数
let segmentNumber = segmentArray.count
// 2.按钮的宽度
witdthFloat = (self.bounds.size.width) / CGFloat(segmentNumber);
// 3.创建按钮
for i in 0...(segmentNumber - 1) {
let btn: UIButton = UIButton.init(frame: CGRect(x: CGFloat(i) * witdthFloat!, y: 0, width: witdthFloat!, height: self.bounds.size.height - 2))
btn.setTitle(segmentArray[i], for: UIControlState.normal)
btn.titleLabel?.font = self.titleFont
btn.setTitleColor(self.titleColor, for: UIControlState.normal)
btn.setTitleColor(self.selectedColor, for: UIControlState.selected)
btn.tag = i + 1;
//设置点击事件
btn.addTarget(self, action: #selector(changeSegumentAction(btn:)), for: UIControlEvents.touchUpInside)
let indicatorView = UIView(frame: CGRect(x: CGFloat(i) * witdthFloat!, y: self.bounds.size.height - 2, width: witdthFloat!, height: 2))
if 0 == I {
indicatorView.backgroundColor = self.selectionIndicatorColor
}
else
{
indicatorView.backgroundColor = self.normalIndicatorColor
}
self.addSubview(indicatorView)
self.indicatorSource?.append(indicatorView)
self.addSubview(btn)
self.btnTitleSource?.append(btn)
self.btnTitleSource?.first?.isSelected = true
}
}
根据传入的选项个数,默认第一个选中,分别向数组中添加按钮和指示器的View,然后添加到父类View。
btn.addTarget(self, action: #selector(changeSegumentAction(btn:)), for: UIControlEvents.touchUpInside)
这句代码是为每个button设置点击事件,响应的事件为touchUpInside。
5)按钮事件的响应
//点击事件
func changeSegumentAction(btn: UIButton) {
self.selectSegment(segment: btn.tag - 1)
}
//改变样式,代理事件
func selectSegment(segment: Int) {
if selectedSegment != segment {
let selectedBtn: UIButton = self.btnTitleSource![selectedSegment!]
selectedBtn.isSelected = false
let segmentBtn: UIButton = self.btnTitleSource![segment]
segmentBtn.isSelected = true
}
for i in 0...((self.indicatorSource?.count)! - 1) {
if i == segment {
self.indicatorSource![i].backgroundColor = self.selectionIndicatorColor
}
else {
self.indicatorSource![i].backgroundColor = self.normalIndicatorColor
}
}
selectedSegment = segment
//代理
self.segmentDelegate?.segmentSelected(selectedSegment!)
}
通过判断当前选择的项的tag来设置文字颜色和指示器背景颜色,最后设置代理事件。
6)代理的流程(自定义选项卡的使用)
swift的代理的流程和OC基本一致。
首先,要实现协议,继承NSObjectProtocol:
/按钮点击事件代理
@objc(LWSegmentedDelegate)
protocol LWSegmentedDelegate: NSObjectProtocol {
func segmentSelected(_ selection:Int)
}
协议需要实现segmentSelected方法,参数为当前选择的按钮的tag。
其中@objc(代理名)是为了对OC可见,必须添加这个才能对外暴露。
然后在定义了代理协议的swift类中定一个代理对象:
var segmentDelegate: LWSegmentedDelegate?
第三步,在按钮事件中用代理实现协议方法:
self.segmentDelegate?.segmentSelected(selectedSegment!)
第四步,在使用自定义选项卡的OC的****ViewController中引入头文件,
#import "项目名-Swift.h"
然后遵循协议:
@interface ViewController ()
使用swift创建自定义选项卡,将self传入参数设置代理,并添加到ViewController的View中。
LWSegmentedControl * segment = [LWSegmentedControl build:CGRectMake(0, 44, self.view.bounds.size.width, 44)
titleDataSource:btnDataSource
backgroundColor:[UIColor colorWithRed:253.0f/255 green:239.0f/255 blue:230.0f/255 alpha:1.0f]
titleColor:[UIColor grayColor]
titleFont:[UIFont fontWithName:@".Helvetica Neue Interface" size:16.0f]
selectColor:[UIColor orangeColor]
normalIndicatorColor:[UIColor grayColor]
selectionIndicatorColor:[UIColor redColor] delegate:self];
最后,在VIewController中实现代理协议中的方法
#pragma mark swift中的代理
- (void)segmentSelected:(NSInteger)selection
{
if (selection == 0) {
NSLog(@"我是button1");
}else if (selection == 1){
NSLog(@"我是button2");
}else{
NSLog(@"我是button3");
}
}
源码中使用到的swift的for循环有必要做一下分析。
传统的for循环在swift3.0被取消:
for i in 0..((self.indicatorSource?.count)! - 1) //等同代码 fot( int i = 0 ; i < self.indicatorSource?.count - 1 ; i++ ){ print(i) }
for i in 0...((self.indicatorSource?.count)! - 1) //等同代码 for( int i = 0 ; i <= self.indicatorSource?.count - 1 ; i++ ){ print(i) }
四、总结
自定义选项卡中涉及swift开发的一些基础流程以及混编的注意事项,对于从OC切换到swift的童鞋应该很有帮助。
项目还有待优化,抛砖引玉,快速实现功能,目的是讲解流程。
代码已经开源,需要的请访问:https://github.com/longup/LWSegmentedControl