Assuming you want each "spot" to start a "countdown from 60" when it is tapped, here's a simple example...
We'll create a UIView subclass with a label and round border.
It will also have a "startTime" property:
private var startTime: Date?
when we Tap the view, we'll set the start time to "now":
startTime = Date()
and we'll have a function to update the countdown:
public func timerFired(_ now: Date) {
if let st = startTime {
let elapsedSeconds = Int(floor(now.timeIntervalSinceReferenceDate - st.timeIntervalSinceReferenceDate))
label.text = "\(60 - elapsedSeconds)"
// if we've reached 60-seconds, set startTime to nil
// so we don't keep going to -1, -2, -3 etc
if elapsedSeconds == 60 {
self.startTime = nil
}
}
}
In our controller, we will have a single repeating timer that fires every 0.3 seconds. If we fire it only once-per-second, we could "skip" numbers:
// use less-than 1-second for the timeInterval... otherwise, we *could* end up with
// skipped seconds. That is, we might see "60 59 58 56 55 53 etc"
Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: true)
Each time the timer func fires, we'll tell each "spot" view by calling .timerFired(), and the "spot" view will then compare its own startTime with the new time and update its label.
So, here's our custom SpotView class:
class SpotView: UIView {
private var startTime: Date?
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.numberOfLines = 0
label.text = "Unlock\nto See"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor),
])
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 1
let g = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
addGestureRecognizer(g)
}
override func layoutSubviews() {
super.layoutSubviews()
let m: CGFloat = min(bounds.width, bounds.height)
layer.cornerRadius = m * 0.5
}
@objc func gotTap(_ g: UITapGestureRecognizer) {
// set the startTime property to "now"
startTime = Date()
label.text = "60"
}
public func timerFired(_ now: Date) {
if let st = startTime {
let elapsedSeconds = Int(floor(now.timeIntervalSinceReferenceDate - st.timeIntervalSinceReferenceDate))
label.text = "\(60 - elapsedSeconds)"
// if we've reached 60-seconds, set startTime to nil
// so we don't keep going to -1, -2, -3 etc
if elapsedSeconds == 60 {
self.startTime = nil
}
}
}
}
and a sample controller:
class FourSpotsVC: UIViewController {
var theSpots: [SpotView] = []
override func viewDidLoad() {
super.viewDidLoad()
let w: CGFloat = 100.0
let h: CGFloat = 100.0
var x: CGFloat = 40.0
var y: CGFloat = 100.0
// let's add 4 "Spot" views
for i in 0..<4 {
x = i % 2 == 0 ? 40.0 : 160.0
let v = SpotView()
v.frame = CGRect(x: x, y: y, width: w, height: h)
view.addSubview(v)
theSpots.append(v)
y += h + 20.0
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// use less-than 1-second for the timeInterval... otherwise, we *could* end up with
// skipped seconds. That is, we might see "60 59 58 56 55 53 etc"
Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: true)
}
@objc func timerFunc() {
let n = Date()
theSpots.forEach { v in
v.timerFired(n)
}
}
}
And it looks like this when running (I tapped the Top Spot, then waited about 10-seconds and tapped the Second Spot):
