Search code examples
iosswiftipad

Drawing app with timer. Timers begins to lag after drawing for less than 20 seconds


I have built this app with the help of some friends. I don't really know how the code works.

Basically using an apple pencil it records data (time on tablet, speed of apple pencil, stroke counts etc). However as more time elapses and more drawing occurs, the timer gets out of sync with real time.

The purpose of this app is for dementia research, I get patients to draw on the tablet, and i collect information of that. I can't do the research if the timer stinks.

I have tried disabling all the timers, but the lag remains the same. I have a felling it has something to do with how strokes are being sampled. I just need a stroke count I don't need it to show strokes per min (which is what it is currently doing). I think the stroke counter might the cause???

this is the program: https://drive.google.com/open?id=1lwzKwG7NLcX1qmE5yoxsdq5HICV2TNHm

class StrokeSegment {
    var sampleBefore: StrokeSample?
    var fromSample: StrokeSample!
    var toSample: StrokeSample!
    var sampleAfter: StrokeSample?
    var fromSampleIndex: Int

    var segmentUnitNormal: CGVector {
        return segmentStrokeVector.normal!.normalized!
    }

    var fromSampleUnitNormal: CGVector {
        return interpolatedNormalUnitVector(between: previousSegmentStrokeVector, and: segmentStrokeVector)
    }

    var toSampleUnitNormal: CGVector {
        return interpolatedNormalUnitVector(between: segmentStrokeVector, and: nextSegmentStrokeVector)
    }

    var previousSegmentStrokeVector: CGVector {
        if let sampleBefore = self.sampleBefore {
            return fromSample.location - sampleBefore.location
        } else {
            return segmentStrokeVector
        }
    }

    var segmentStrokeVector: CGVector {
        return toSample.location - fromSample.location
    }

    var nextSegmentStrokeVector: CGVector {
        if let sampleAfter = self.sampleAfter {
            return sampleAfter.location - toSample.location
        } else {
            return segmentStrokeVector
        }
    }

    init(sample: StrokeSample) {
        self.sampleAfter = sample
        self.fromSampleIndex = -2
    }

    @discardableResult
    func advanceWithSample(incomingSample: StrokeSample?) -> Bool {
        if let sampleAfter = self.sampleAfter {
            self.sampleBefore = fromSample
            self.fromSample = toSample
            self.toSample = sampleAfter
            self.sampleAfter = incomingSample
            self.fromSampleIndex += 1
            return true
        }
        return false
    }
}

class StrokeSegmentIterator: IteratorProtocol {
    private let stroke: Stroke
    private var nextIndex: Int
    private let sampleCount: Int
    private let predictedSampleCount: Int
    private var segment: StrokeSegment!

    init(stroke: Stroke) {
        self.stroke = stroke
        nextIndex = 1
        sampleCount = stroke.samples.count
        predictedSampleCount = stroke.predictedSamples.count
        if (predictedSampleCount + sampleCount) > 1 {
            segment = StrokeSegment(sample: sampleAt(0)!)
            segment.advanceWithSample(incomingSample: sampleAt(1))
        }
    }

    func sampleAt(_ index: Int) -> StrokeSample? {
        if index < sampleCount {
            return stroke.samples[index]
        }
        let predictedIndex = index - sampleCount
        if predictedIndex < predictedSampleCount {
            return stroke.predictedSamples[predictedIndex]
        } else {
            return nil
        }
    }

    func next() -> StrokeSegment? {
        nextIndex += 1
        if let segment = self.segment {
            if segment.advanceWithSample(incomingSample: sampleAt(nextIndex)) {
                return segment
            }
        }
        return nil
    }
}

for example at true 25 seconds, the app displays the total time at 20 seconds.


Solution

  • A Timer is not something to count elapsed time. It is a tool used to trigger an execution after some time has elapsed. But just "after" some time has elapsed, not "exactly after" some time has elapsed. So for instance doing something like:

    var secondsElapsed: TimeInterval = 0.0
    let timeInitiated = Date()
    
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
        secondsElapsed += 1
        print("\(secondsElapsed) seconds should have passed but in reality \(Date().timeIntervalSince(timeInitiated)) second have passed")
    }
    

    you will see that the two are not the same but are pretty close. But as soon as I add some extra work like this:

    var secondsElapsed: TimeInterval = 0.0 let timeInitiated = Date()

    func countTo(_ end: Int) {
        var string = ""
        for i in 1...end {
            string += String(i)
        }
        print("Just counted to string of lenght \(string.count)")
    }
    
    Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { _ in
        countTo(100000)
    }
    
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
        secondsElapsed += 1
        print("\(secondsElapsed) seconds should have passed but in reality \(Date().timeIntervalSince(timeInitiated)) second have passed")
    }
    

    we get to situations like "14.0 seconds should have passed but in reality 19.17617702484131 second have passed".

    We made the application busy so it doesn't have time to count correctly.

    In your case you will need to use one of two solutions:

    1. If you are interested in time elapsed simply use timeIntervalSince as demonstrated in first code snippet.
    2. If you need to ensure triggering every N seconds you should optimize your code, consider multithreading... But mostly keep in mind that you can only get close to "every N seconds", it should not be possible to guarantee an execution exactly every N seconds.