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

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.

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:
If you are interested in time elapsed simply use timeIntervalSince as demonstrated in first code snippet.
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.

Related

How do I animate a number in swift?

I want to animate a number. The animation I want to achieve is going from 0 increasing all the way up to the current number (at high speed). In this project, the number is the number of steps a user has taken. Is there a way this can be achieved?
LazyVStack{
ForEach(steps, id: \.id) { step in
//Here is the number I want to be animated
Text("\(step.count)")
.font(.custom(customFont, size: 50))
Text("Steps")
.font(.custom(customFont, size: 25))
.multilineTextAlignment(.center)
}
}
I believe I have a function along the right lines, I just need to apply it! Here is the function:
func addNumberWithRollingAnimation() {
withAnimation {
// Decide on the number of animation tasks
let animationDuration = 1000 // milliseconds
let tasks = min(abs(self.enteredNumber), 100)
let taskDuration = (animationDuration / tasks)
// add the remainder of our entered num from the steps
total += self.enteredNumber % tasks
// For each task
(0..<tasks).forEach { task in
// create the period of time when we want to update the number
// I chose to run the animation over a second
let updateTimeInterval = DispatchTimeInterval.milliseconds(task * taskDuration)
let deadline = DispatchTime.now() + updateTimeInterval
// tell dispatch queue to run task after the deadline
DispatchQueue.main.asyncAfter(deadline: deadline) {
// Add piece of the entire entered number to our total
self.total += Int(self.enteredNumber / tasks)
}
}
}
}
Here is a utility function called Timer.animateNumber() which takes a Binding<Int> to animate, a Binding<Bool> busy which indicates if the value is currently animating, and Int start value, an Int end value, and a Double duration in seconds.
To use it, you need to define an #State private var number: Int to animate, and #State private var busy: Bool to keep track of the animation's state. This can also be used to terminate the animation early by just setting busy to false. Pass in your start value, end value, and duration in seconds.
This demo shows two animated numbers. The first counts up from 1 to 10000 in 1 second. The second counts down from 20 to 0 in 20 seconds. The Stop All button can be used to stop both animations.
extension Timer {
static func animateNumber(number: Binding<Int>, busy: Binding<Bool>, start: Int, end: Int, duration: Double = 1.0) {
busy.wrappedValue = true
let startTime = Date()
Timer.scheduledTimer(withTimeInterval: 1/120, repeats: true) { timer in
let now = Date()
let interval = now.timeIntervalSince(startTime)
if !busy.wrappedValue {
timer.invalidate()
}
if interval >= duration {
number.wrappedValue = end
timer.invalidate()
busy.wrappedValue = false
} else {
number.wrappedValue = start + Int(Double(end - start)*(interval/duration))
}
}
}
}
struct ContentView: View {
#State private var number: Int = 0
#State private var number2: Int = 0
#State private var busy: Bool = false
#State private var busy2: Bool = false
var body: some View {
VStack(spacing: 20) {
Text(String(number))
Button("Go") {
if !busy {
Timer.animateNumber(number: $number, busy: $busy, start: 1, end: 10000, duration: 1)
}
}
Text(String(number2))
Button("Go") {
if !busy2 {
Timer.animateNumber(number: $number2, busy: $busy2, start: 20, end: 0, duration: 20)
}
}
Button("Stop All") {
busy = false
busy2 = false
}
}
}
}

Swift - How to get the current position of AVAudioPlayerNode while it's looping?

I have an AVAudioPlayerNode looping a segment of a song:
audioPlayer.scheduleBuffer(segment, at: nil, options:.loops)
I want to get current position of the song while it's playing. Usually, this is done by calculating = currentFrame / audioSampleRate
where
var currentFrame: AVAudioFramePosition {
guard let lastRenderTime = audioPlayer.lastRenderTime,
let playerTime = audioPlayer.playerTime(forNodeTime: lastRenderTime) else {
return 0
}
return playerTime.sampleTime
}
However, when the loop ends and restarts, the currentFrame does not restart. But it still increases which makes currentFrame / audioSampleRate incorrect as the current position.
So what is the correct way to calculate the current position?
Good old modulo will do the job:
public var currentTime: TimeInterval {
guard let nodeTime = player.lastRenderTime,
let playerTime = player.playerTime(forNodeTime: nodeTime) else {
return 0
}
let time = (Double(playerTime.sampleTime) / playerTime.sampleRate)
.truncatingRemainder(dividingBy: Double(file.length) / Double(playerTime.sampleRate))
return time
}

how to calculate time between receiving variables in swift

I have a variable that will changes every millisecond or so.
I want to calculate the time between them correctly without delay.
I want to know how long takes to get the new one.
Is that possible in swift?
I know that there is a timer in swift but according to apple documentation:
that's not exact.i need to get the millisecond time between each
receiving variable.
Use a property observer didSet with Date arithmetic to compute the interval between changes.
Here is an example:
class ViewController: UIViewController {
private var setTime: Date?
private var intervals = [Double]()
var value: Int = 0 {
didSet {
let now = Date()
if let previous = setTime {
intervals.append(now.timeIntervalSince(previous) * 1000)
}
setTime = now
}
}
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...20 {
value = i
}
print(intervals)
}
}
Console output
[0.0020265579223632812, 0.12600421905517578, 0.00095367431640625, 0.0050067901611328125, 0.0010728836059570312, 0.00095367431640625, 0.00095367431640625, 0.0010728836059570312, 0.00095367431640625, 0.0020265579223632812, 0.00095367431640625, 0.0010728836059570312, 0.00095367431640625, 0.0, 0.0010728836059570312, 0.00095367431640625, 0.00095367431640625, 0.0020265579223632812, 0.0010728836059570312]
You can capture time whenever the value changes and calculate the difference. like:
var yourVar: Int {
willSet {
//you can capture the time here
}
didSet {
//or here
}
}
var _variable = 0
var variable : Int {
get{
return _variable
}
set{
let start = DispatchTime.now()
_variable = newValue
let dst = start.distance(to: DispatchTime.now())
print("Interval = \(dst)")
}
}
variable = 1
variable = 2
Output:
Interval = nanoseconds(2239301)
Interval = nanoseconds(69482)

In Gameplaykit, how can I add a delay GKGoal time in GKAgent2D behavior?

I have a GKEntity that has a GKAgent2D component. Its behaviors are GKGoal, toWander: and toStayOnPath:maxPredictionTime:. The entity wanders continuously in the scene; however, I would like it to stop wandering for a while. For example, if the entity is a sheep that wanders about, I would like it to stop periodically to eat and, after a delay, start wandering again.
UPDATE:
In the Entity:
addComponent(MoveIdleComponent(maxSpeed: 60, maxAcceleration: 6, radius: Float(node.texture!.size().width * 0.3), entityManager: entityManager))
MoveIdleComponent
class MoveIdleComponent : GKAgent2D, GKAgentDelegate {
let entityManager: EntityManager
init(maxSpeed: Float, maxAcceleration: Float, radius: Float, entityManager: EntityManager) {
self.entityManager = entityManager
super.init()
delegate = self
self.maxSpeed = maxSpeed
self.maxAcceleration = maxAcceleration
self.radius = radius
print(self.mass)
self.mass = 0.01
}
func agentWillUpdate(agent: GKAgent) {
guard let spriteComponent = entity?.componentForClass(SpriteComponent.self) else {
return
}
self.position = float2(spriteComponent.node.position)
}
func agentDidUpdate(agent: GKAgent) {
guard let spriteComponent = entity?.componentForClass(SpriteComponent.self) else {
return
}
spriteComponent.node.position = CGPoint(position)
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
behavior = WanderBehavoir(targetSpeed: maxSpeed)
}
}
WanderBehavoir:
class WanderBehavoir: GKBehavior {
init(targetSpeed: Float) {
super.init()
if targetSpeed > 0 {
setWeight(0.5, forGoal: GKGoal(toWander: targetSpeed))
}
}
}
How can I do this?
Thanks in advance
There doesn't seem to be a GKGoal(toEatFood:withFrequency:) API, so you'll have to step back a bit toandthink about how to set the agent's goals to achieve your goals.
If you want the agent to stop wandering, or stop following a path, for some period of time, what you want is for those to no longer be its goals. (And furthermore, you want it to stop, not continue with whatever direction and speed when you took away its goals, so you'll want to introduce a toReachTargetSpeed: goal for a speed of zero.)
There are two general ways to do this:
Have your behavior include wander, follow-path, and speed (of zero) goals, with weights set such that wander and follow-path outweigh speed. When you want to switch between wander+path behavior and stopping behavior, use setWeight(_:forGoal:) to make the speed goal outweigh the others.
Have one behavior that includes wander and follow-path goals, and another with a speed (of zero) goal, and set the agent's behavior property when you want to switch between them.
Ok, i found a solution with #rickster suggestion. I post it if can help someone.
I've added pause value.
if pause is true, the weight of speed change to 1, with GKGoal(toReachTargetSpeed: 0)
in WanderBehavoir class:
class WanderBehavoir: GKBehavior {
init(targetSpeed: Float, entityManager: EntityManager, pause: Bool) {
super.init()
var weightWander : Float = 1.0
var weightSpeed : Float = 0.0
if pause {
weightWander = 0
weightSpeed = 1
}
// | |
// --A--B--
// | |
if targetSpeed > 0 {
let lato = Float(500.0)
let pts = [vector_float2(-lato,0),vector_float2(+lato,0)]
let path = GKPath(points: UnsafeMutablePointer(pts), count: pts.count, radius: 980, cyclical: true)
let obstacleNode = entityManager.nodesWithUnitType(Tree)
let obstacles = SKNode.obstaclesFromNodePhysicsBodies(obstacleNode)
setWeight(0.5, forGoal: GKGoal(toAvoidObstacles: obstacles, maxPredictionTime: 0.5))
setWeight(0.2, forGoal: GKGoal(toStayOnPath: path, maxPredictionTime: 0.5))
setWeight(weightWander, forGoal: GKGoal(toWander: targetSpeed))
setWeight(weightSpeed, forGoal: GKGoal(toReachTargetSpeed: 0))
}
}
}
In class class MoveIdleComponent : GKAgent2D, GKAgentDelegate pause switch true and false after delta time randomly
private var lastUpdateInterval: NSTimeInterval = 0
var setPause = true
var randomTimeStop = NSTimeInterval(Int(5...8))
var randomTimeMove = NSTimeInterval(Int(10...20))
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
lastUpdateInterval += seconds
if setPause {
if lastUpdateInterval > randomTimeStop {
setPause = !setPause
lastUpdateInterval = 0
randomTimeMove = NSTimeInterval(Int(10...20))
}
}
else {
if lastUpdateInterval > randomTimeMove {
setPause = !setPause
lastUpdateInterval = 0
randomTimeStop = NSTimeInterval(Int(5...8))
}
}
print("randomTimeMove \(randomTimeMove)")
behavior = WanderBehavoir(targetSpeed: maxSpeed, entityManager: entityManager, pause: setPause)
}

2 Timers at the same time

I need to use 2 timers at the same time: one for a session and one for exercices.
The one for exercices can be either a timer or a count down depending on the exercices.
I used this class for the 2 timers but I have a delay of 1s: I think it's a problem of thread. I tried to dispatch but not working.
Moreover the class is not accurate since refresh may take longer than specified, based on the available resources.
Do you have a solution to resolve my issue: having 2 accurate timers at same which can be timer or count down ?
Thank you for your help
public class Chronometer: NSObject {
//
// MARK: - Private Properties
private var startTime = NSTimeInterval(0)
private var accumulatedTime = NSTimeInterval(0)
private var elapsedSinceLastRefresh = NSTimeInterval(0)
private var timer = NSTimer()
//
// MARK: - Public Properties
public var elapsedTime: NSTimeInterval {
return elapsedSinceLastRefresh + accumulatedTime
}
/// The Time Interval to refresh the chronometer. The default is 1 second
public var refreshInterval = NSTimeInterval(1)
/// Determines a time limit for this Chronometer
public var timeLimit: NSTimeInterval?
/// Optional Block that gets called on each refresh of the specified time interval.
/// If this class needs to update UI, make sure that the UI class are made in the
/// main thread, or dispatched into it.
public var updateBlock: ((NSTimeInterval, NSTimeInterval?) -> ())?
/// Optional Block that gets called when the chronometer reach its limit time
public var completedBlock: (() -> ())?
//
// MARK: - Initializers
///
/// A convenience initializer that allow specifying the refresh interval
/// :param: refreshInterval The desired refresh interval
///
public convenience init(refreshInterval: NSTimeInterval) {
self.init()
self.refreshInterval = refreshInterval
}
///
/// A convenience initializer that allow specifying the refesh interval and update block
/// :param: refreshInterval The desired refresh interval
/// :param: updateBlock The update block to be called on each refresh
///
public convenience init(refreshInterval: NSTimeInterval, updateBlock: (NSTimeInterval, NSTimeInterval?) -> ()) {
self.init()
self.refreshInterval = refreshInterval
self.updateBlock = updateBlock
}
//
// MARK: - Internal Methods
///
/// Refresh the timer, calling the update block to notify the tracker about the new value.
///
func refreshTime() {
// Calculate the new time
var refreshTime = NSDate.timeIntervalSinceReferenceDate()
self.elapsedSinceLastRefresh = (refreshTime - startTime)
// Calculates the remaining time if applicable
var remainingTime: NSTimeInterval? = nil
if self.timeLimit != nil {
remainingTime = self.timeLimit! - elapsedTime
}
// If an update block is specified, then call it
self.updateBlock?(elapsedTime, remainingTime)
// If the chronometer is complete, then call the block and
if let limit = self.timeLimit {
if self.elapsedTime >= limit {
self.stop()
self.completedBlock?()
}
}
}
//
// MARK: - Public Methods
///
/// Set a time limit for this chronometer
///
public func setLimit(timeLimit: NSTimeInterval, withCompletionBlock completedBlock: () -> ()) {
self.timeLimit = timeLimit
self.completedBlock = completedBlock
}
///
/// Start the execution of the Cronometer.
/// Start will take place using accumulated time from the last session.
/// If the Cronometer is running the call will be ignored.
///
public func start() {
if !timer.valid {
// Create a new timer
timer = NSTimer.scheduledTimerWithTimeInterval(self.refreshInterval,
target: self,
selector: "refreshTime",
userInfo: nil,
repeats: true)
// Set the base date
startTime = NSDate.timeIntervalSinceReferenceDate()
}
}
///
/// Stops the execution of the Cronometer.
/// Keeps the accumulated value, in order to allow pausing of the chronometer, and
/// to keep "elapsedTime" property value available for the user to keep track.
///
public func stop() {
timer.invalidate()
accumulatedTime = elapsedTime
elapsedSinceLastRefresh = 0
}
///
/// Resets the Cronometer.
/// This method will stop chronometer if it's running. This is necessary since
/// the class is not thread safe.
///
public func reset() {
timer.invalidate()
elapsedSinceLastRefresh = 0
accumulatedTime = 0
}
}
In my UIViewController, in the viewDidLoad():
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { [unowned self] in
self.chronometerWorkout = Chronometer(refreshInterval: NSTimeInterval(0.01)) {
(elapsedTime: NSTimeInterval, remainingTime: NSTimeInterval?) in
dispatch_async(dispatch_get_main_queue()) {
if let r = remainingTime {
self.counterView?.chronoWorkoutLabel.text = r.getFormattedInterval(miliseconds: false)
} else {
self.secondsChronoWorkout = elapsedTime
self.counterView?.chronoWorkoutLabel.text = elapsedTime.getFormattedInterval(miliseconds: false)
}
}
}
}
self.chronometerExo = Chronometer(refreshInterval: NSTimeInterval(0.01)) {
(elapsedTime: NSTimeInterval, remainingTime: NSTimeInterval?) in
if let r = remainingTime {
self.counterView?.chronoExoLabel.text = r.getFormattedInterval(miliseconds: false)
self.graphicView.yValues[self.selectedRow] = Double(remainingTime!)
self.counterView?.circularTimerProgressView.progress = CGFloat(remainingTime!)
} else {
self.secondsChronoExo = elapsedTime
self.counterView?.chronoExoLabel.text = elapsedTime.getFormattedInterval(miliseconds: false)
self.graphicView.yValues[self.selectedRow] = Double(elapsedTime)
self.counterView?.circularTimerProgressView.progress = CGFloat(elapsedTime)
}
}
UPDATED:
var startWorkOutDate:CFAbsoluteTime!
var startExoTime:CFAbsoluteTime!
var timeReference:Double!
func updateTimerLabels() {
let elapsedTimeWorkOut = CFAbsoluteTimeGetCurrent() - startWorkOutDate
self.counterView?.chronoWorkoutLabel.text = String(format: "%.2f",elapsedTimeWorkOut)
let startExoTime = self.startExoTime ?? 0.0
let elapsedTimeExo = CFAbsoluteTimeGetCurrent() - startExoTime
if timeReference != 0 {
if elapsedTimeExo <= timeReference {
let remainingTimeExo = timeReference - elapsedTimeExo
self.counterView?.chronoExoLabel.text = String(format: "%.2f",remainingTimeExo)
}
}
}
func startTimers(indexPath: NSIndexPath){
...
if self.startWorkoutChronometer == false {
startWorkOutDate = CFAbsoluteTimeGetCurrent()
self.startWorkoutChronometer = true
self.startExoChronometer = true
_ = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("updateTimerLabels"), userInfo: nil, repeats: true)
}
if self.startExoChronometer == true {
if timeReference != 0 {
if self.graphicView.yValues[selectedRow] == timeReference {
startExoTime = CFAbsoluteTimeGetCurrent()
} else {
timeReference = Double(Int(self.graphicView.yValues[selectedRow]))
}
}
}
}
Consider not using 1 NSTimer instance per timer. Use an NSTimer, or maybe even display link' only to trigger updates to the UI.
For your timers, just record the start time as an NSDate and the timer type. Provide a method to ask for the current time, or display value. The timer can then calculate that on the fly based on the current device time.
In this way you will have no slip or offset and you can update the UI as fast as you like.

Resources