-
Notifications
You must be signed in to change notification settings - Fork 0
/
TableViewCell.swift
181 lines (162 loc) · 7.28 KB
/
TableViewCell.swift
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//
// TableViewCell.swift
// ToDoApp
//
// Created by Nikita Vlasenko on 11/11/16.
// Copyright © 2016 Nikita Vlasenko. All rights reserved.
//
import UIKit
import QuartzCore
import RealmSwift
protocol TableViewCellDelegate {
// indicates that the given item has been deleted
func toDoItemDeleted(todoItem: ToDoItem)
func showDetailed(toDoItem: ToDoItem)
}
class TableViewCell: UITableViewCell {
let realm = try! Realm()
let gradientLayer = CAGradientLayer()
var originalCenter = CGPoint()
var deleteOnDragRelease = false, completeOnDragRelease = false
var tickLabel: UILabel, crossLabel: UILabel
let label: StrikeThroughText
var itemCompleteLayer = CALayer()
// The object that acts as delegate for this cell.
var delegate: TableViewCellDelegate?
// The item that this cell renders.
var toDoItem: ToDoItem? {
didSet {
label.text = toDoItem!.title
label.strikeThrough = toDoItem!.completed
itemCompleteLayer.hidden = !label.strikeThrough
}
}
required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
// create a label that renders the to-do item text
label = StrikeThroughText(frame: CGRect.null)
label.textColor = UIColor.whiteColor()
label.font = UIFont.boldSystemFontOfSize(16)
label.backgroundColor = UIColor.clearColor()
// utility method for creating the contextual cues
func createCueLabel() -> UILabel {
let label = UILabel(frame: CGRect.null)
label.textColor = UIColor.whiteColor()
label.font = UIFont.boldSystemFontOfSize(32.0)
label.backgroundColor = UIColor.clearColor()
return label
}
// tick and cross labels for context cues
tickLabel = createCueLabel()
tickLabel.text = "\u{2713}"
tickLabel.textAlignment = .Right
crossLabel = createCueLabel()
crossLabel.text = "\u{2717}"
crossLabel.textAlignment = .Left
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(label)
// remove the default blue highlight for selected cells
selectionStyle = .None
// gradient layer for cell
gradientLayer.frame = bounds
let color1 = UIColor(white: 1.0, alpha: 0.2).CGColor as CGColorRef
let color2 = UIColor(white: 1.0, alpha: 0.1).CGColor as CGColorRef
let color3 = UIColor.clearColor().CGColor as CGColorRef
let color4 = UIColor(white: 0.0, alpha: 0.1).CGColor as CGColorRef
gradientLayer.colors = [color1, color2, color3, color4]
gradientLayer.locations = [0.0, 0.01, 0.95, 1.0]
layer.insertSublayer(gradientLayer, atIndex: 0)
// gesture recognizer
var recognizerPan = UIPanGestureRecognizer(target: self, action: "handlePan:")
recognizerPan.delegate = self
addGestureRecognizer(recognizerPan)
var recognizerTap = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
addGestureRecognizer(recognizerTap)
addSubview(label)
addSubview(tickLabel)
addSubview(crossLabel)
// add a layer that renders a green background when an item is complete
itemCompleteLayer = CALayer(layer: layer)
itemCompleteLayer.backgroundColor = UIColor(red: 0.0, green: 0.6, blue: 0.0,
alpha: 1.0).CGColor
itemCompleteLayer.hidden = true
layer.insertSublayer(itemCompleteLayer, atIndex: 0)
}
let kLabelLeftMargin: CGFloat = 15.0
let kUICuesMargin: CGFloat = 10.0, kUICuesWidth: CGFloat = 50.0
override func layoutSubviews() {
super.layoutSubviews()
// ensure the gradient layer occupies the full bounds
gradientLayer.frame = bounds
itemCompleteLayer.frame = bounds
label.frame = CGRect(x: kLabelLeftMargin, y: 0,
width: bounds.size.width - kLabelLeftMargin,
height: bounds.size.height)
tickLabel.frame = CGRect(x: -kUICuesWidth - kUICuesMargin, y: 0,
width: kUICuesWidth, height: bounds.size.height)
crossLabel.frame = CGRect(x: bounds.size.width + kUICuesMargin, y: 0,
width: kUICuesWidth, height: bounds.size.height)
}
func handlePan(recognizer: UIPanGestureRecognizer) {
// 1
if recognizer.state == .Began {
// when the gesture begins, record the current center location
originalCenter = center
}
// 2
if recognizer.state == .Changed {
let translation = recognizer.translationInView(self)
center = CGPointMake(originalCenter.x + translation.x, originalCenter.y)
// has the user dragged the item far enough to initiate a delete/complete?
deleteOnDragRelease = frame.origin.x < -frame.size.width / 2.0
completeOnDragRelease = frame.origin.x > frame.size.width / 2.0
// fade the contextual clues
let cueAlpha = fabs(frame.origin.x) / (frame.size.width / 2.0)
tickLabel.alpha = cueAlpha
crossLabel.alpha = cueAlpha
// indicate when the user has pulled the item far enough to invoke the given action
tickLabel.textColor = completeOnDragRelease ? UIColor.greenColor() : UIColor.whiteColor()
crossLabel.textColor = deleteOnDragRelease ? UIColor.redColor() : UIColor.whiteColor()
}
// 3
if recognizer.state == .Ended {
let originalFrame = CGRect(x: 0, y: frame.origin.y,
width: bounds.size.width, height: bounds.size.height)
if deleteOnDragRelease {
if delegate != nil && toDoItem != nil {
// notify the delegate that this item should be deleted
delegate!.toDoItemDeleted(toDoItem!)
}
} else if completeOnDragRelease {
if toDoItem != nil {
try! realm.write {
toDoItem!.completed = true
}
}
label.strikeThrough = true
itemCompleteLayer.hidden = false
UIView.animateWithDuration(0.2, animations: {self.frame = originalFrame})
} else {
UIView.animateWithDuration(0.2, animations: {self.frame = originalFrame})
}
}
}
func handleTap(recognizer: UIPanGestureRecognizer) {
if delegate != nil && toDoItem != nil {
// notify the delegate that this item should be deleted
delegate!.showDetailed(toDoItem!)
}
}
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let translation = panGestureRecognizer.translationInView(superview!)
if fabs(translation.x) > fabs(translation.y) {
return true
}
return false
}
return false
}
}