Detecting when person wearing an Apple Watch falls - apple-watch

If I drop my Apple Watch and catch it before it hits the ground, the app I'm making should detect that the watch has fallen. But that's not happening. What's wrong with the code below? Thanks!
let motion = CMMotionManager()
if motion.isDeviceMotionAvailable {
motion.deviceMotionUpdateInterval = 0.1
motion.startDeviceMotionUpdates()
if let deviceMotion = motion.deviceMotion {
let accelerationX = deviceMotion.gravity.x + deviceMotion.userAcceleration.x
let accelerationY = deviceMotion.gravity.y + deviceMotion.userAcceleration.y
let accelerationZ = deviceMotion.gravity.z + deviceMotion.userAcceleration.z
let totalAcceleration = sqrt((accelerationX * accelerationX) + (accelerationY * accelerationY) + (accelerationZ * accelerationZ))
if totalAcceleration > 9.0 {
print("Watch has fallen")
}
}
motion.stopDeviceMotionUpdates()
}

motion.deviceMotion will just get the latest sample of device-motion data.
So, this might just fetch the data once on when you run it. You will probably need something like a timer to check the acceleration.
Something like this(taken from https://developer.apple.com/documentation/coremotion/getting_raw_accelerometer_events)
let motion = CMMotionManager()
func startAccelerometers() {
// Make sure the accelerometer hardware is available.
if self.motion.isAccelerometerAvailable {
self.motion.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz
self.motion.startAccelerometerUpdates()
// Configure a timer to fetch the data.
self.timer = Timer(fire: Date(), interval: (1.0/60.0),
repeats: true, block: { (timer) in
// Get the accelerometer data.
if let data = self.motion.accelerometerData {
let x = data.acceleration.x
let y = data.acceleration.y
let z = data.acceleration.z
// Use the accelerometer data in your app.
}
})
// Add the timer to the current run loop.
RunLoop.current.add(self.timer!, forMode: .defaultRunLoopMode)
}
}
Alternatively, you can also pass a handler to startDeviceMotionUpdates, which will be called based on deviceMotionUpdateInterval.

Related

Dispatch queue to animate SCNNode in ARKit

I'm facing an issue when trying to periodically animate my nodes on an ARSession. I'm fetching data from Internet every 5 seconds and then with that data I update this nodes (shrink or enlarge).
My code looks something like this:
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
fetchDataFromServer() {
let fetchedData = $0
DispatchQueue.main.async {
node1.update(fetchedData)
node2.update(fetchedData)
node3.update(fetchedData)
}
if stopCondition { timer.invalidate() }
}
}
Problem is that when calling the updates I'm seeing a glitch in which the camera seems to freeze for a fraction of second and I see the following message in the console: [Technique] World tracking performance is being affected by resource constraints [1]
Update happens correctly, but the UX is really clumpsy if every 5 seconds I get these "short freezes"
I've tried creating a concurrent queue too:
let animationQueue = DispatchQueue(label: "animationQueue", attributes: DispatchQueue.Attributes.concurrent)
and call animationQueue.async instead of main queue but problem persists.
I'd appreciate any suggestions.
EDIT: Each of the subnodes on it's update method looks like this
private func growingGeometryAnimation(newHeight height: Float) -> CAAnimation{
// Change height
let grow = CABasicAnimation(keyPath: "geometry.height")
grow.toValue = height
grow.fromValue = prevValue
// .... and the position
let move = CABasicAnimation(keyPath: "position.y")
let newPosition = getNewPosition(height: height)
move.toValue = newPosition.y + (yOffset ?? 0)
let growGroup = CAAnimationGroup()
growGroup.animations = [grow, move]
growGroup.duration = 0.5
growGroup.beginTime = CACurrentMediaTime()
growGroup.timingFunction = CAMediaTimingFunction(
name: kCAMediaTimingFunctionEaseInEaseOut)
growGroup.fillMode = kCAFillModeForwards
growGroup.isRemovedOnCompletion = false
growGroup.delegate = self
return growGroup
}
self.addAnimation(growingGeometryAnimation(newHeight: self.value), forKey: "bar_grow_animation")
To make any updates to the scene use SCNTransaction, it makes sure all of the changes are made on the appropriate thread.
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
fetchDataFromServer() {
let fetchedData = $0
SCNTransaction.begin()
node1.update(fetchedData)
node2.update(fetchedData)
node3.update(fetchedData)
SCNTransaction.commit()
if stopCondition { timer.invalidate() }
}
}

Smooth animation with timer and loop in iOS app

I have ViewController with stars rating that looks like this (except that there are 10 stars)
When user opens ViewController for some object that have no rating I want to point user's attention to this stars with very simple way: animate stars highlighting (you could see such behaviour on some ads in real world when each letter is highlighted one after another).
One star highlighted
Two stars highlighted
Three stars highlighted
......
Turn off all of them
So this is the way how I am doing it
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
func ratingStarsAnimation() {
for i in 1...11 {
var timer : Double = 0.6 + Double(i)*0.12
delayWithSeconds(timer) {
ratingStars.rating = (i < 10) ? Double(i) : 0
}
}
}
What is going on here? I have function called delayWithSeconds that delays action and I use this function to delay each star highlighting. And 0.6 is initial delay before animation begins. After all stars are highlighted - last step is to turn off highlighting of all stars.
This code works but I can't say that it is smooth.
My questions are:
How can I change 0.6 + Double(i)*0.12 to get smooth animation feel?
I think that my solution with delays is not good - how can I solve smooth stars highlighting task better?
Have a look at the CADisplaylink class. Its a specialized timer that is linked to the refresh rate of the screen, on iOS this is 60fps.
It's the backbone of many 3rd party animation libraries.
Usage example:
var displayLink: CADisplayLink?
let start: Double = 0
let end: Double = 10
let duration: CFTimeInterval = 5 // seconds
var startTime: CFTimeInterval = 0
let ratingStars = RatingView()
func create() {
displayLink = CADisplayLink(target: self, selector: #selector(tick))
displayLink?.add(to: .main, forMode: .defaultRunLoopMode)
}
func tick() {
guard let link = displayLink else {
cleanup()
return
}
if startTime == 0 { // first tick
startTime = link.timestamp
return
}
let maxTime = startTime + duration
let currentTime = link.timestamp
guard currentTime < maxTime else {
finish()
return
}
// Add math here to ease the animation
let progress = (currentTime - startTime) / duration
let progressInterval = (end - start) * Double(progress)
// get value =~ 0...10
let normalizedProgress = start + progressInterval
ratingStars.rating = normalizedProgress
}
func finish() {
ratingStars.rating = 0
cleanup()
}
func cleanup() {
displayLink?.remove(from: .main, forMode: .defaultRunLoopMode)
displayLink = nil
startTime = 0
}
As a start this will allow your animation to be smoother. You will still need to add some trigonometry if you want to add easing but that shouldn't be too difficult.
CADisplaylink:
https://developer.apple.com/reference/quartzcore/cadisplaylink
Easing curves: http://gizma.com/easing/

Changing the time interval of a Timer in swift spritekit

So, I have this timer that is setup to run a specific function (which are both shown below) on a time interval variable called 'frequency' when I try and change the timeinterval variable frequency to a lower number based on the score number it doesn't seem to change the rate at which it fires it just seems to fire at the same time even if the frequency is changed to a lower number
override func didMove(to view: SKView) {
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
func spawnFallingOjects() {
if (GameState.current == .playing || GameState.current == .blackstone) {
guard usingThirdEye == false else { return }
let scoreLabel = childNode(withName: "scoreLabel") as! Score
let lane = [-100, -50 , 0, 50, 100]
let duration = 3.0
switch scoreLabel.number {
case 0...50:
frequency = 6.0
print("frequency has changed: \(frequency)")
case 51...100:
frequency = 4.5
print("frequency has changed: \(frequency)")
case 101...200000:
frequency = 1.1
print("frequency has changed: \(frequency)")
default:
return
}
let randomX = lane[Int(arc4random_uniform(UInt32(lane.count)))]
let object:Object = Object()
object.createFallingObject()
object.position = CGPoint(x: CGFloat(randomX), y: self.size.height)
object.zPosition = 20000
addChild(object)
let action = SKAction.moveTo(y: -450, duration: duration)
object.run(SKAction.repeatForever(action))
}
}
How do I make the timer fire faster when the frequency number changes to a lower number? should I recreate the timer at the end of the function?
You should actually avoid using Timer, Sprite kit has its own time functionality, and Timer does not work well with it and is a real pain to manage.
Instead, use SKAction's to wait and fire:
let spawnNode = SKNode()
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration:frequency)
let spawn = SKAction.run(spawnFallingObjects)
spawnNode.run(SKAction.repeatForever(SKAction.sequence([wait,spawn])))
addChild(spawnNode)
}
Then to make it faster, just do:
switch scoreLabel.number {
case 0...50:
spawnNode.speed = 1
print("speed has changed: \(spawnNode.speed)")
case 51...100:
spawnNode.speed = 1.5
print("speed has changed: \(spawnNode.speed)")
case 101...200000:
spawnNode.speed = 2
print("speed has changed: \(spawnNode.speed)")
default:
return
}
The timeInterval property of Timer is a readonly property. (And your code is not trying to write a new frequency into the property...)
should I recreate the timer at the end of the function?
Nearly yes. Just you have no need to do it at the end.
With changing your method header like this:
func spawnFallingOjects(_ timer: Timer) {
You can access the fired Timer through the parameter timer, so you may need to write something like this just after switch scoreLabel.number {...}:
if frequency != timer.timeInterval {
//Invalidate old Timer...
timer.invalidate()
//And then allocate new one
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
You can modify the fireDate property of an existing Timer (in case which still isValid), but recreating a Timer instance is not a heavy operation (comparing to creating an SKSpriteNode instance), so recreating a new Timer seems to be a little bit easier.

Images don't update when loaded until end

I am trying to write a routine to roll a die. I want the face to change several times before landing on the final number. The code below does not show the die changing faces. I added the sleep statement in hopes that it would give it time to update but it just stays with the initial face until the end and then shows the final face. Is there a statement to add after changing the texture to force the view to update?
func rollDice() {
var x: Int
for var i = 0; i < 50; i++
{
x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
pieces[3].texture = dice[0].faces[x]
NSThread.sleepForTimeInterval(0.1)
print(x)
}
}
As has been pointed out in the comments, the screen only redraws on the main thread. Therefore, you could let the dice rolls take place on a background thread, and redraw the screen on the main thread:
func rollDice() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
for i in 0..<50 {
let x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
dispatch_async(dispatch_get_main_queue) {
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
pieces[3].texture = dice[0].faces[x]
NSThread.sleepForTimeInterval(0.1)
print(x)
}
}
}
}
Thank you for the help. After reading some about Timers, I came up with the following code:
func rollDice() {
rollCount = 0
timer = NSTimer.scheduledTimerWithTimeInterval(0.05, target:self, selector: "changeDie", userInfo: nil, repeats: true)
}
func changeDie () {
x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
print(x)
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
++rollCount
if rollCount == 20 {
timer.invalidate()
}
}

How to make a smooth transition between drum patterns using AVAudioPlayer?

I am writing a music app which will let the user switch between different drum patterns "smoothly" by pressing desired buttons on screen. (By "smoothly" I mean the pattern will switch in the measure immediately following the time when the user presses a button).
My problem is that the time delay I am calculating for a delayed start of the next pattern is slightly larger than desired. I can fix by reducing the time delay by 0.1 sec, but this may not work if one uses a different tempo than the one I am currently using for testing.
The code which calculates the delay is:
func startClock() {
let aSelector : Selector = "updateClock"
clock = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = CFAbsoluteTimeGetCurrent()
}
func stopClock() {
clock.invalidate()
}
func updateClock() {
currentTime = CFAbsoluteTimeGetCurrent()
elapsedTime = currentTime - startTime
elapsedBeats = UInt(elapsedTime / audioMeterUpdateInterval)
elapsedMeasures = UInt( Double(elapsedBeats) / Double(beatUnit[patternSelectIdx]) )
requiredMeasures = elapsedMeasures + 1
requiredBeats = requiredMeasures * UInt(beatUnit[patternSelectIdx])
requiredTime = Double(requiredBeats) * audioMeterUpdateInterval
delayTime = requiredTime - elapsedTime - 0.1 // 0.1 sec is chosen ad hoc
}
The code for one of the buttons for ending the drum patterns is:
#IBAction func endShort(sender: UIButton) {
fileName = patternSelect + "End1"
if startPlay == true {
play(fileName, numberOfLoops: 0, delaySec: delayTime)
} else {
play(fileName, numberOfLoops: 0, delaySec: 0)
}
playPauseButton.setImage(playImage, forState: UIControlState.Normal)
startPlay = false
}
Finally, the function play called by the above code is
func play(fileName: String, numberOfLoops: Int, delaySec: Double){
if delaySec > 0 {
let delayNSec = Double(NSEC_PER_SEC)*delaySec
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayNSec))
self.ButtonAudioPlayer.numberOfLoops = numberOfLoops
self.ButtonAudioPlayer.volume = 1.0
self.ButtonAudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fileName, ofType: "wav")!)
dispatch_after(dispatchTime, dispatch_get_main_queue(),{
self.ButtonAudioPlayer = try! AVAudioPlayer(contentsOfURL: self.ButtonAudioURL)
self.ButtonAudioPlayer.play()
})
} else {
ButtonAudioPlayer.numberOfLoops = numberOfLoops
ButtonAudioPlayer.volume = 1.0
ButtonAudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fileName, ofType: "wav")!)
ButtonAudioPlayer = try! AVAudioPlayer(contentsOfURL: ButtonAudioURL)
ButtonAudioPlayer.play()
}
}
Is there an alternate approach to this? Also, can the 0.1 sec be a fixed delay related to the button animation time and hence always remain fixed and can be modified? Thanks!