自定义选项卡实战swift(内附源码且精讲知识点)

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<UIButton>?

    //定义未选中的字体颜色

    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<UIView>?

}

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<String>, 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<String>) {

        // 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 () <LWSegmentedDelegate>

使用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

推荐阅读更多精彩内容

  • 孩子放学回家和我说了一件事情,中午在学校午休的时侯,突然觉得自己眼睛睁不开,呼吸困难,最后使出全身的劲醒了过...
    勿今心阅读 21评论 0 0
  • 一切的晚霞都是上天的眷顾
    帅帅黎小黎阅读 16评论 0 0
  • 我曾在年少时,想过一夜白头时的情景。 不过说到底我此时的年龄也不过十六,还是未成年的年龄,却经历的事情比成年人...
    顾长歌阅读 36评论 0 0
  • 01. 在和朋友偶尔的聊天中,她突然问了这样一个问题:“如果孩子成绩下降了,你会严厉指责孩子还是安慰鼓励呢?” 我...
    不再迷离阅读 447评论 8 6
  • 微蒙若有光, 红霞结成霜。 情人不在场, 可怜好斜阳。
    舞央阅读 13评论 0 0