iOS自定义控件 - 时间轴

时间轴是大家都很熟悉的特效,在网络上也有很多现成的资源,虽然轮子已经有很多,但是我们自己尝试来做一个也不失为是一种乐趣,下面我们通过 StoryBoard 和代码来完成一个时间轴的界面。

配置 StoryBoard:

首先我们创建新的 Project ,删掉默认的 ViewController.swift,和 MainStoryBoard 中的 ViewControlle r Scene ,现在我们就有了一个空的项目。

下面我们在 Main.StoryBoard 中拖进来一个 TableViewController 给它命名为 NoteTableViewController ,然后创建 New File - File ,选择 Cocoa Touch Class ,继承自 UITableViewController ,命名为 NoteTableViewController

然后将 NoteTableViewControllerNoteTableViewController.Class 进行绑定,并给Main.StoryBoard 设置一个 Identity ,并勾选 Is Initial View Controller

下一步,选中 NoteTableViewController 下的 TableView ,将它的 Row Height 设置为60,Prototype Cells 设置为2。

下面,设置两个 Cell ,分别命名为 RightCell 和 LeftCell ,给每个 Cell 添加 3个Buttonh 和 1个UIView。3个按钮命名依次为:middle delete edit ,宽度30,高度30,并给按钮设置对应的背景图片,并让3个按钮对齐,UIView 的宽度是2,高度30,背景颜色为黑色。

LeftCell 添加一个Label,文本设置为右对齐,给 RightCell 添加一个 Label,文本设置为左对齐。

最后,勾选 delete edit 两个按钮的 Hidden 选项,然后创建 New File - File ,选择 Cocoa Touch Class ,继承自 UITableViewCell ,命名为 NoteTableViewCell

自定义 TableViewCell

NoteTableViewCell 中创建两个子类,继承自 NoteTableViewCell

1
2
3
4
5
6
7
class LeftNoteTableViewCell: NoteTableViewCell {
}
class RightNoteTableViewCell: NoteTableViewCell {
}

RightCell 和 LeftCell 和这两个子类绑定,并给这两个子类拖进来两个控件:

1
2
3
4
5
6
7
class LeftNoteTableViewCell: NoteTableViewCell {
@IBOutlet weak var descriptionLabel: UILabel!
}
class RightNoteTableViewCell: NoteTableViewCell {
@IBOutlet weak var descriptionLabel: UILabel!
}

将三个通用的控件拖进 NoteTableViewCell 这个基类中,并设置一个 Bool 值:

1
2
3
4
5
6
7
class NoteTableViewCell: UITableViewCell {
@IBOutlet weak var deleteButton: UIButton!
@IBOutlet weak var editButton: UIButton!
@IBOutlet weak var middleButton: UIButton!
var isExpand = false
}

创建模型

新建一个文件命名为 NoteItem ,创建结构体并嵌入枚举:

1
2
3
4
5
6
7
8
9
10
struct NoteItem {
var cost: Float
var content: String
var type: NoteItemType
}
enum NoteItemType: Int {
case income
case outcome
}

使用模型:

回到 NoteTableViewCell 中,创建配置方法 configureCell 将模型中的数据传给 Cell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func configureCell(withItem item: NoteItem) {
switch item.content {
case "购物":
middleButton.setImage(UIImage(named: "shopping.png"), forState: .Normal)
case "服装":
middleButton.setImage(UIImage(named: "clothes.png"), forState: .Normal)
case "食品":
middleButton.setImage(UIImage(named: "restaurant.png"), forState: .Normal)
case "工资","奖金":
middleButton.setImage(UIImage(named: "income.png"), forState: .Normal)
default:
middleButton.setImage(UIImage(named: "middle_button.png"), forState: .Normal)
}
let lableConeten = "\(item.content) \(item.cost)"
setValue(lableConeten, forKeyPath: "descriptionLabel.text")
}

回到 NoteTableViewController 中,完善模型的数据:

1
2
3
4
5
6
7
8
var datas: [NoteItem] = [NoteItem(cost: 1000, content: "购物", type: .outcome),
NoteItem(cost: 3000, content: "宠物", type: .outcome),
NoteItem(cost: 5000, content: "工资", type: .income),
NoteItem(cost: 2000, content: "购物", type: .outcome),
NoteItem(cost: 10000, content: "奖金", type: .income),
NoteItem(cost: 4000, content: "服装", type: .outcome),
NoteItem(cost: 8000, content: "食品", type: .outcome),
NoteItem(cost: 6000, content: "购物", type: .outcome),]

配置 tableView 的行数:

1
2
3
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas.count
}

自定义 Cell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = datas[indexPath.row]
var identifier = ""
switch item.type {
case .income:
identifier = "LeftCell"
case .outcome:
identifier = "RightCell"
}
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as? NoteTableViewCell
return cell!
}

回到 NoteTableViewCell 中创建点击事件,让 middleButton 在点击时弹出隐藏的 deleteedit

1
2
3
4
5
6
7
8
@IBAction func middieButtonClicked(sender: AnyObject) {
if !isExpand {
expand()
}
else {
close()
}
}

实现 expand()close() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func expand() {
editButton.hidden = false
deleteButton.hidden = false
UIView.animateWithDuration(0.5, animations: {
self.deleteButton.center.x = 30
self.editButton.center.x = self.frame.size.width - 30
}) { (done) in
self.isExpand = true
}
}
func close() {
UIView.animateWithDuration(0.5, animations: {
self.deleteButton.center = self.middleButton.center
self.editButton.center = self.middleButton.center
}) { (done) in
self.deleteButton.hidden = true
self.editButton.hidden = true
self.isExpand = false
}
}

设置代理

deleteedit 的点击事件传递到 TableViewController

1
2
3
protocol NoteCellDelegate: class {
func noteCellClickMiddleButtom(sender: NoteTableViewCell)
}

设置代理对象:

1
weak var delegate: NoteCellDelegate?

新建一个文件,命名为 NoteTableViewContorller+NoteCellDelegate ,将代理方法添加为扩展方法:

1
2
3
4
5
6
7
extension NoteTableViewController: NoteCellDelegate {
func noteCellClickMiddleButtom(sender: NoteTableViewCell){
let indexPath = tableView.indexPathForCell(sender)
print("cell \(indexPath?.row) clicked")
}
}

回到 NoteTableViewCell 中,完善 middieButtonClicked 方法:

1
2
3
4
5
6
7
8
9
10
11
@IBAction func middieButtonClicked(sender: AnyObject) {
if !isExpand {
expand()
}
else {
close()
}
self.delegate?.noteCellClickMiddleButtom(self)
}

回到 NoteTableViewController 中,完善自定义 Cell 的方法,使用代理调用 configureCell 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = datas[indexPath.row]
var identifier = ""
switch item.type {
case .income:
identifier = "LeftCell"
case .outcome:
identifier = "RightCell"
}
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as? NoteTableViewCell
if cell?.delegate == nil {
cell?.delegate = self
}
cell?.configureCell(withItem: item)
return cell!
}

最后,添加 tableViewtableHeaderView ,原型基本上就完成了:

1
2
3
4
let frame = CGRectMake(0, 0, 100, 70)
let headerView = UIView(frame: frame)
headerView.backgroundColor = UIColor.redColor()
tableView.tableHeaderView = headerView

下面将完整代码贴出来方便大家参考。

完整代码:

NoteTableViewController 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class NoteTableViewController: UITableViewController {
var datas: [NoteItem] = [NoteItem(cost: 1000, content: "购物", type: .outcome),
NoteItem(cost: 3000, content: "宠物", type: .outcome),
NoteItem(cost: 5000, content: "工资", type: .income),
NoteItem(cost: 2000, content: "购物", type: .outcome),
NoteItem(cost: 10000, content: "奖金", type: .income),
NoteItem(cost: 4000, content: "服装", type: .outcome),
NoteItem(cost: 8000, content: "食品", type: .outcome),
NoteItem(cost: 6000, content: "购物", type: .outcome),]
override func viewDidLoad() {
super.viewDidLoad()
let frame = CGRectMake(0, 0, 100, 70)
let headerView = UIView(frame: frame)
headerView.backgroundColor = UIColor.redColor()
tableView.tableHeaderView = headerView
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = datas[indexPath.row]
var identifier = ""
switch item.type {
case .income:
identifier = "LeftCell"
case .outcome:
identifier = "RightCell"
}
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as? NoteTableViewCell
if cell?.delegate == nil {
cell?.delegate = self
}
cell?.configureCell(withItem: item)
return cell!
}
}

NoteTableViewContorller+NoteCellDelegate 文件:

1
2
3
4
5
6
7
extension NoteTableViewController: NoteCellDelegate {
func noteCellClickMiddleButtom(sender: NoteTableViewCell){
let indexPath = tableView.indexPathForCell(sender)
print("cell \(indexPath?.row) clicked")
}
}

NoteTableViewCell 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class NoteTableViewCell: UITableViewCell {
@IBOutlet weak var deleteButton: UIButton!
@IBOutlet weak var editButton: UIButton!
@IBOutlet weak var middleButton: UIButton!
var isExpand = false
weak var delegate: NoteCellDelegate?
func configureCell(withItem item: NoteItem) {
switch item.content {
case "购物":
middleButton.setImage(UIImage(named: "shopping.png"), forState: .Normal)
case "服装":
middleButton.setImage(UIImage(named: "clothes.png"), forState: .Normal)
case "食品":
middleButton.setImage(UIImage(named: "restaurant.png"), forState: .Normal)
case "工资","奖金":
middleButton.setImage(UIImage(named: "income.png"), forState: .Normal)
default:
middleButton.setImage(UIImage(named: "middle_button.png"), forState: .Normal)
}
let lableConeten = "\(item.content) \(item.cost)"
setValue(lableConeten, forKeyPath: "descriptionLabel.text")
}
override func setNilValueForKey(key: String) {
super.setNilValueForKey(key)
print("KVC 的传值目标\(key) 没有找到")
}
@IBAction func middieButtonClicked(sender: AnyObject) {
if !isExpand {
expand()
}
else {
close()
}
self.delegate?.noteCellClickMiddleButtom(self)
}
func expand() {
editButton.hidden = false
deleteButton.hidden = false
UIView.animateWithDuration(0.5, animations: {
self.deleteButton.center.x = 30
self.editButton.center.x = self.frame.size.width - 30
}) { (done) in
self.isExpand = true
}
}
func close() {
UIView.animateWithDuration(0.5, animations: {
self.deleteButton.center = self.middleButton.center
self.editButton.center = self.middleButton.center
}) { (done) in
self.deleteButton.hidden = true
self.editButton.hidden = true
self.isExpand = false
}
}
}
protocol NoteCellDelegate: class {
func noteCellClickMiddleButtom(sender: NoteTableViewCell)
}
class LeftNoteTableViewCell: NoteTableViewCell {
@IBOutlet weak var descriptionLabel: UILabel!
}
class RightNoteTableViewCell: NoteTableViewCell {
@IBOutlet weak var descriptionLabel: UILabel!
}

NoteItem 文件:

1
2
3
4
5
6
7
8
9
10
struct NoteItem {
var cost: Float
var content: String
var type: NoteItemType
}
enum NoteItemType: Int {
case income
case outcome
}