Change vibrate duration on iPhone's without haptic feedback in Swift - swift

I'm creating a simple game in Swift. If the time is almost over (5 seconds) I would like to vibrate the phone.
When 5 or 4 seconds left: normal vibration with AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) once per second.
Question 1:
When 3 or 2 seconds left: short vibration, twice per second.
When 1 second left, short vibration, thrice per second.
Question 2:
When 0 seconds left, vibrate for 2 or 3 seconds.
Is this possible? How can I change the duration of the vibration? I read online that increasing the duration of the vibration is not allowed, thats fine for me, but can I decrease the duration?
I have already checked How to make iPhone vibrate using Swift? but this question from 2014 did not cover Swift 3 or 4. If I try to play the sound 1519, 1520 or 1521 it just does nothing. The answer posted by Malmer only targets iPhone's with a haptic feedback. I want to find a solution for the iPhone 6s and lower, since they still run the newest iOS (11) and are able to run short vibrations when you're browsing through the iOS Settings for notification sounds.

In terms of vibrating the device, possibilities are very limited. As far as I know, this is the only method available (if you need to support all iPhones):
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
From the docs: "function to invoke a brief vibration. On the iPod touch, does nothing.".
You would have to create som logic in order for this to repeat. One solution could be to use the NSTimer API. E.g. scheduledTimerWithTimeInterval.
Example implementation:
var gameTimer: Timer!
// Execute doTimedStuff method every 2 seconds
gameTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(doTimedStuff), userInfo: nil, repeats: true)
// Invalidate timer when done
gameTimer.invalidate()

One option is to use UIFeedbackGenerator to produce haptic feedback. Documentation Link
This allows you to choose from a few pre-set haptic vibrations, which you can fire at your desired time intervals.
Here is a quick example, although make sure you visit the documentation linked above for all the haptic feedback options and best practices.
let feedbackGenerator = UISelectionFeedbackGenerator()
feedbackGenerator.prepare()
feedbackGenerator.selectionChanged()
(Note that this only works on iPhone 7 and newer devices.)

Use .hapticContinuous event type with CHHapticEvent. You can customize the the time of vibration and intensity of vibrations.
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
var events = [CHHapticEvent]()
var relativeTimer = 0.0
let event1 = CHHapticEvent(eventType: .hapticContinuous, parameters: [intensity, sharpness], relativeTime: relativeTimer, duration: longDuration)
relativeTimer += event1.duration
events.append(event1)
do {
let pattern = try CHHapticPattern(events: events, parameters: [])
let player = try engine.makePlayer(with: pattern)
try engine.start()
try player.start(atTime: 0)
} catch {
print("Haptic Error: \(error.localizedDescription).")
}

Related

Update Live Activity once per second - Swift

I'm searching for a way to update the Live Activity every second or every 2 seconds without using Push Notifications while keeping the app in background.
Do you have any suggestions? I tried something like this, but after few seconds it's stop working:
var bgTask = UIBackgroundTaskIdentifier(rawValue: 1324)
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: .default)
As mentioned in the documentation, "The delay between the time you schedule a background task and when the system launches your app to run the task can be many hours".
Unfortunately, background tasks are not suitable for your needs. Using Push notifications is the only way (that I know of at least) to achieve what you are trying to do.
It seems like there is no way to update the Live Activity every single second or in any other too frequent manner. Looks like battery saving is crucial here and system cuts off the updates after a few seconds, even if the application is running in the background.
If you need to display a timer on the LiveActivity, according to the docs, it's possible to use the following initializer of Text which seems to be dedicated for that use case:
init(timerInterval: ClosedRange<Date>, pauseTime: Date? = nil, countsDown: Bool = true, showsHours: Bool = true)
For example:
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
It creates an instance that displays a timer counting within the provided interval. It just counts time automatically so it's not necessary to think about implementing timer on your own. However, there is no way of listening when timer ends to update the activity so looks like it might be necessary to schedule a notification or background task anyway and receive/run and handle it when timer ends.
Seems like apps mentioned here use a mechanism I described above. You have to consider your use case and decide whether Live Activities are suitable for your app.

How to properly measure elapsed time in background in Swift

I have this two functions that measure the elapsed time when the phone is locked or the app is in background:
func saveTimeInBackground(){
startMeasureTime = Int(Date.timeIntervalSinceReferenceDate)
}
func timeOnAppActivated(){
stopMeasureTime = Int(Date.timeIntervalSinceReferenceDate)
elapsedTime = stopMeasureTime - startMeasureTime
seconds = seconds - elapsedTime + 2
if seconds > 0 {
timerLbl.text = "time: \(seconds)"
} else {
seconds = 0
timerLbl.text = "time: \(seconds)"
}
}
and then in the viewDidLoad() i have observers that are trigger the functions when the app becomes active/inactive:
NotificationCenter.default.addObserver(self, selector: #selector(saveTimeInBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(timeOnAppActivated), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)
The problem is that when the app becomes active there are 2 seconds (approximately) of difference so i've added 2 seconds and it seems to work fine, but only if the elapsed time is > 15 seconds.
If i lock the phone and immediately unlock it the there are like 5 or more seconds that are missing. For example, if there are 50 seconds left, when i lock and immediately unlock it there are like 42 seconds left.
Can anyone please explain, what i am doing wrong?
Edit: The logic of the app is this:
It starts a match between 2 players with 60 seconds for a game. The problem is that when one of the players locks the phone the app stop to measure the time. This way if the player1 has 10 seconds left to make a move, the player2 still has 50 seconds left. I'm looking for a reliable way to calculate the time even if the player locks the phone or put the app in background.
Edit 2: I think i figured out what the problem is: I think the issue has to do with the fact that the “seconds” are Int, and the Date not and when it gets converted it’s rounded up. I didn't tested it, but when i ahve the solution i'll post the answer. Thanks all for your time!
You're relying on exact timing of notifications that aren't guaranteed to have any exact timing. There's no guarantee about when, exactly, either of those notifications will arrive, and there's nothing you can do about that. Even your two-second fix is, as you say, approximate. It'll probably be different on different models of iPhone or even at different times on the same iPhone, depending how busy iOS is when you check.
What's more, when you go into the background, you can't be certain that you'll stay there. Once in the background, iOS might decide to terminate your app at any time.
I'm not sure what the goal is here but I think you'll need to reconsider what you want to do and see if there's some other approach. Your current two-second hack will, at best, spawn a bunch of other hacks (like the 15 second threshold you mention) without ever being especially accurate. And then it'll probably all break in the next iOS update when some iOS change causes the timing to change.
I would use Date object to track game time.
func gameStart() {
gameStartDate = Date()
}
func timeOnAppActivated() {
let secondsLeft = 60 - abs(gameStartDate?.timeIntervalSinceNow ?? 0)
if secondsLeft > 0 {
timerLbl.text = "time: \(secondsLeft)"
} else {
timerLbl.text = "time: 0"
}
}
Ok, like I mention in the edit 2 of the question:
The first issue was because "seconds" is a Int and then it almost always gains or lose when converting it from Double.
But the main problem was that i had to invalidate the timer when the app enter in background and i didn't.
So now with invalidating the timer when the app gets the notification that will enter background and then starting it when it enter foreground everything works fine.
To test this properly call those methods on button click. It may be coz of delay in releasing some resources in background.

HTML5 Audio: Setting currentTime and then playing on iPhone spontaneously rewinds 50-100 milliseconds

I'm attempting to play audio from a specific location inside a file while also receiving updates on the playback using the onTimeUpdate event. I examine the currentTime property within the onTimeUpdate handler so that I can provide visual feedback on where we are in the file.
My problem is that when I set currentTime to a known value and then call play(), the first time I get the currentTime property in my onTimeUpdate handler, it will be as I set it. But the next time onTimeUpdate is called, it's actually a smaller value than I originally started playing from, sometimes more than 100 ms smaller!
For example:
player.currentTime = 2.0;
player.play();
Then, outputting player.currentTime from within my onTimeUpdate handler:
2
1.95 // WTF??
2.24
2.56
...
The problem is that this causes my UI widgets that indicate the current location in the track to jump around. I have a clunky workaround that I am implementing, but I'd like to know if there is any known way to stop this from happening, or if I'm just not doing something right.
To my knowledge, it only happens on iPhone. I'm testing with an iPhone 6 and iOS 10.1.1.
Here's a demonstration if you'd like to see this for yourself.
http://codepen.io/brianrak/pen/7db413c4431827fe3318e99ae497cf2a?editors=1010
Thanks in advance!
var second = ("0" + parseInt(player.currentTime % 60)).slice(-2);
var minutes = ("0" + parseInt((player.currentTime / 60) % 60)).slice(-2);

swift - Delay a spawn function using NSTimer

I'm looking to integrate some delayed functions into my game, currently I'm working on putting a 5 second delay on calling my first function as in this time period the game goes through a countdown to get the player ready.
Currently my countdown is displayed but the game is in full swing in the background. I've got one function I'd like to delay for 5 seconds but I'm also thinking of using the same method to incorporate other objects later in the game to boost difficulty.
The call I'm looking to delay is moving.addChild(crows)
If anyone can show me how to build that would be great as I've been unable to find on site (unless I've missed it).
Don't use NSTimer in SpriteKit. Use this instead:
let delay = SKAction.waitForDuration(5.0)
someNode.runAction(delay) {
//run code here after 5 secs
}
let delay = 5.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
moving.addChild(crows)
}

Change duration of SKAction playSoundFileNamed

How do i change the duration of a created sound created with SKAction.playSoundFileNamed:
var sound = SKAction.playSoundFileNamed("sound.mp3", waitForCompletion: false)
I tried setting sound.duration = 1.0 but with no luck so far ?
In short, that is impossible with SKAction.playSoundFileNamed: method.
duration property specifies the time in seconds required for current action to complete.
When using SKAction.playSoundFileNamed: method you don't have control over the audio being played (no pausing, stoping, resuming pitching etc). It's meant to play a sound, nothing more.
Some useful links:
https://stackoverflow.com/a/22590464/3402095
https://stackoverflow.com/a/21023417/3402095