Hi there I am completing some research for Alzheimers. And have realised I need to make this app - i have very little experience. I want to be able to record the time it takes to complete a drawing. Both the time spend with apple pencil on tablet and the time spent overall to complete a drawing (time on tablet plus time in between strokes).
I have created this application so far but can't get the timer to work.
I have the drawing/scribble board down pat.
I have tried many different approaches but I am just not experienced enough in code to work out why it is not starting the timer when the apple pencil hits the tablet. The code below is for the ViewController script.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var canvasView: CanvasView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
@IBAction func clearCanvas(_ sender: Any) { //this button will clear canvas and clear timers!
canvasView.clearCanvas()
timer.invalidate()
seconds = 0 //Here we manually enter the restarting point for the seconds, but it would be wiser to make this a variable or constant.
timeDrawing.text = "\(seconds)"
}
@IBOutlet weak var timeDrawing: UILabel! //This is one of the label used to display the time drawing (i have yet to add the label to display the time inbetween strokes)
var seconds = 0
var timer = Timer()
var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
var resumeTapped = false
var touchPoint:CGPoint!
var touches:UITouch!
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
@objc func updateTimer() {
seconds += 1
timeDrawing.text = "\(seconds)" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
runTimer()
} else {
timer.invalidate()
}
}
}
}
I was hoping when the apple pencil touched the tablet it would start the timer. And then when the pencil left the tablet it would stop one of the timers. (i have yet to add another timer for the time in between strokes, any help with that would be appreciated too.)
You don’t need a timer to measure the amount of time something takes. Rather, you need timestamps. That is, you take a timestamp at the start, take a timestamp at the end, and subtract the first from the second.
The easiest way to deal with timestamps is using Date. For example:
let start1 = Date()
NSLog("Hello Cruel World!")
let end1 = Date()
let start2 = Date()
print("Hello Cruel World!")
let end2 = Date()
print(end1.timeIntervalSince(start1)) // 0.0007849931716918945
print(end2.timeIntervalSince(start2)) // 0.0014280080795288086
[Incidentally, the fact that NSLog was faster than print, even if for only this single data point, was very surprising to me.]
The only problem with using Date is that you’ll see weird behaviours if the system clock changes. You can get around this using lower-level APIs — for example, clock_gettime with CLOCK_MONOTONIC_RAW, see its man page for details — but you can probably gloss over that point until you get more things working.
You don’t need a timer to measure the amount of time something takes.
Hmmm, I re-read your question and it’s clear that the above, while true, is not the whole story. You will need a timer for updating your elapsed time label. My recommendation is that you disconnect this from your time accounting. That is, using timestamps, as discussed in my earlier post, to account for time, and then just using the timer to render the current elapsed time to a label.
ok so I have re done it with time labels but now how do I do what you said above to make it so that it will store the time drawing:
this is the stopwatch swift file
import Foundation
class Stopwatch {
// MARK: Private Properties
private let step: Double
private var timer: Timer?
//The time when counting was started
private(set) var from: Date?
//The time when counting was stopped
private(set) var to: Date?
// The time when user pause timer last one
private var timeIntervalTimelapsFrom: TimeInterval?
// The total time before user paused timer
private var timerSavedTime: TimeInterval = 0
typealias TimeUpdated = (_ time: Double)->Void
let timeUpdated: TimeUpdated
// MARK: Public Properties
var isPaused: Bool {
return timer == nil
}
//MARK: Initialization
init(step: Double = 1.0, timeUpdated: @escaping TimeUpdated) {
self.step = step
self.timeUpdated = timeUpdated
}
deinit {
print("⏱ Stopwatch successfully deinited")
deinitTimer()
}
func toggle() {
guard timer != nil else {
initTimer()
return
}
deinitTimer()
}
func stop() {
deinitTimer()
from = nil
to = nil
timerSavedTime = 0
timeUpdated(0)
}
private func initTimer() {
let action: (Timer)->Void = { [weak self] timer in
guard let strongSelf = self else {
return
}
let to = Date().timeIntervalSince1970
let timeIntervalFrom = strongSelf.timeIntervalTimelapsFrom ?? to
let time = strongSelf.timerSavedTime + (to - timeIntervalFrom)
strongSelf.timeUpdated(round(time))
}
if from == nil {
from = Date()
}
if timeIntervalTimelapsFrom == nil {
timeIntervalTimelapsFrom = Date().timeIntervalSince1970
}
timer = Timer.scheduledTimer(withTimeInterval: step,
repeats: true, block: action)
}
private func deinitTimer() {
//Saving last timelaps
if let timeIntervalTimelapsFrom = timeIntervalTimelapsFrom {
let to = Date().timeIntervalSince1970
timerSavedTime += to - timeIntervalTimelapsFrom
}
//Invalidating
timer?.invalidate()
timer = nil
timeIntervalTimelapsFrom = nil
}
}
this is the drawing view file:
import UIKit
/*
TODO: choose what needed: drawwing without delay or smoothing
*/
class DrawingView: UIView {
//MARK: Properties
var lineColor: UIColor!
var lineWidth: CGFloat! = 5 {
didSet {
bezierPath.lineWidth = lineWidth
}
}
private var bufferImages: [UIImage] = []
private var bezierPath: UIBezierPath!
private var bufferImage: UIImage?
private var bezierCurvePoints: [CGPoint] = []
private var isCleanNeeded = false
var timer: Stopwatch!
//MARK: Outlets
@IBOutlet private weak var undo: UIButton?
//MARK: Init
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
// backgroundColor = .clear
isMultipleTouchEnabled = false
bezierPath = UIBezierPath()
// bezierPath.usesEvenOddFillRule = true
bezierPath.lineJoinStyle = .round
bezierPath.lineCapStyle = .round
bezierPath.lineWidth = lineWidth
addGestureRecognizer()
}
//MARK: Draw
private func addGestureRecognizer() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(viewDragged(_:)))
addGestureRecognizer(panGesture)
}
override func draw(_ rect: CGRect) {
guard !isCleanNeeded else {
initialize()
isCleanNeeded = false
return
}
bufferImage?.draw(in: rect)
drawLine()
}
private func drawLine() {
lineColor?.setStroke()
bezierPath.close()
bezierPath.stroke()
}
@IBAction private func clean() {
isCleanNeeded = true
bufferImage = nil
bufferImages = []
self.setNeedsDisplay()
undo?.isEnabled = false
}
@IBAction private func back() {
if !bufferImages.isEmpty {
initialize()
bufferImages.removeLast()
bufferImage = bufferImages.last
setNeedsDisplay()
if bufferImages.isEmpty {
undo?.isEnabled = false
}
}
}
private var lastPoint: CGPoint! // A point for storing the last position
private var pointCounter: Int = 0 // A counter of points
private let pointLimit: Int = 32 // A limit of the points
@objc func viewDragged(_ sender: UIPanGestureRecognizer) {
let point = sender.location(in: self)
guard point.x > 0, point.x < frame.width, point.y > 0, point.y < frame.height else {
return
}
switch sender.state {
case .began:
lastPoint = point
pointCounter = 0
timer.toggle()
case .changed:
bezierPath.move(to: lastPoint)
bezierPath.addLine(to: point)
lastPoint = point
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
saveBufferImage()
setNeedsDisplay()
bezierPath.removeAllPoints()
}
else {
setNeedsDisplay()
}
case .ended:
pointCounter = 0
saveBufferImage()
setNeedsDisplay()
if let bufferImage = bufferImage {
bufferImages.append(bufferImage)
undo?.isEnabled = true
}
// bezierPath.removeAllPoints()
timer.stop()
default:
break
}
}
/**
Save line and update view
*/
private func saveBufferImage() {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
if bufferImage != nil {
bufferImage?.draw(in: bounds)
}
drawLine()
bufferImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
func isEmpty() -> Bool {
return bufferImage == nil && bezierPath.isEmpty
}
}