How to set a timer for a string to trigger periodically? - swift

I'm making a FlashCards app and I would like to set a timer for the cards in a way so that if the user knows the answer, the card will trigger again next day, then if he knows it again, it will trigger in 5 days and so on.
I haven't found anything related to this, any help with it?

Get track of the Dates and the User's performance with each card. (You can use a Timer here. For instance, if the User 'knows' the answer within a minute, the card will be marked as 'correct' (known), and beyond that time, the card will be marked as 'wrong' (not yet mastered and needs to be repeated).
Create a logic wherein the cards marked as 'wrong' will popup after a few days or so.
I suggest you use CoreData to save the Dates when the User uses the Flashcard app.
Also, you will need to learn how to use DateComponents().
Here's a great resource.

In case when users don't quit app you can use Timer. However, this is only expected for short intervals.
let minute = 60
let anotherTimeInterval = 2 * minute
var timer = Timer.scheduledTimer(timeInterval: minute, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: false)
#objc func updateTimer() {
// do something
timer.invalidate()
timer = Timer.scheduledTimer(timeInterval: anotherTimeInterval, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: false)
}
Normally, you need to prepare a specific data source for a specific day. For example, in your case, you can add a flag or date to each card and use that flag or date to add that card to the data source on a specific date.

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 detect (say) 2 seconds of inactivity in swift?

This question could be rephrased as: How to invoke a function if 2 seconds pass without an event (re)occurring?
I'm playing with SFSpeechRecogniser. While the user is speaking it sends continuous updates (maybe 2-3 per second). I'm trying to detect when the user stops speaking. If I don't receive any updates for (say) 2 seconds, I can assume that the user has paused speaking.
How to implement this in Swift?
I am aware that I could do:
var timer : Timer?
func f() {
on_event = { result, error in
print( "Got event, restarting timer" )
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
print( "2s inactivity detected" )
self.timer?.invalidate()
NotificationCenter.default.post( name: inactivity_notification, object: nil )
}
}
}
But is it possible to do it without repeatedly creating and destroying the Timer instance (and thus creating a boatload of temporary Timer instances that never get used)?
One way to do it is to:
Record the current time when an event occurs
Set up a recurring timer with a granularity you are comfortable with (for example 0.25 seconds).
When the timer pops, check difference between current time and last event time. If that is greater than 2 seconds, fire your notification.
This is what I'd do if I had to recognize that a person had stopped typing for 2 seconds. Invalidating and creating timers at typing speed would be a lot of churn. You can tune this to your requirements depending on how close to exactly 2 seconds you need to be.
You could also do this by just having a timeSinceLastEvent variable, and set it to 0 when an event occurs. The recurring timer would increment this by the granularity, and check if it has reached 2 seconds and fire the notification if it had. This is cruder than doing the time math since the timer interval isn't guaranteed, but simpler.
Timer's .fireDate property is writable.
So every time a speech event occurs just do timer.fireDate = Date(timeIntervalSinceNow: 2)

swift 4 Timer to start at specific time, then do the interval

I want to call a method at the same time every day (at the end of the day, like 23.59).
I understand to set a repeating method to be called I do like this:
let date = Date()
let timer = Timer(fireAt: date, interval: 86400, target: self, selector:
#selector(runCode), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
This repeats every 86400 seconds, which mean every day.
But how to set the date parameter to start at 23.59, the specific time I want. Currently, it just goes off at the time the code calls it.
Thanks very much
sounds like you might have to do silent notifications:
Silent Push Notification Payload
What is difference between remote notification and silent notification in iOS?
There is also a UNNotifcationRequest you can look into but it's not silent:
Use UNNotificationRequest (UserNotificationFramework) for data only local notification on iOS

How Do I Manage Scheduled Events in Swift 4?

Looking for some direction on how to best manage scheduled events in swift 4 (either external library or Apple support).
I would like to be able to put a task (with a timer) in to some kind of hub that will then execute the task once the timer has run down and then perform the task's completion block at the end of the task.
Can I do this with NSNotification?
Anyone know of a good example app (e.g. GitHub) that I can download and play with to learn how to schedule tasks.
More detail
I am creating timers like this
timer_iosActionsRefresh = Timer(fireAt: date, interval: timeInterval, target: self, selector: #selector(refreshIosActions), userInfo: nil, repeats: true)
RunLoop.main.add( timer_iosActionsRefresh!, forMode: RunLoopMode.commonModes)
I then do this to stop timers
if timer_iosActionsRefresh != nil {
timer_iosActionsRefresh?.invalidate()
timer_iosActionsRefresh = nil
}
This works great in app foregoround mode but does not work in background mode since Apple only supports special states for background mode activities (e.g. CLLocationManager didEnterRegion)
1) Is it possible to create something like a Timer but that will still execute in background mode? Not looking to do heavy or continuous work, just small background queries.
2) Even if there is some way around Timers not working in background mode, will that code make it through Apple app review?
I would recomment using GCD (Grand Central Dispatch), which is fairly easy:
let queue = DispatchQueue.global(qos: .background) // or some higher QOS level
// Do somthing after 10.5 seconds
queue.asyncAfter(deadline: .now() + 10.5) {
// your task code here
DispatchQueue.main.async {
// maybe update UI here after task has finished
}
}
If you dont need a background queue and want to perform the task simply in the main thread, just do:
DispatchQueue.main.asyncAfter(deadline: .now() + 10.5) {
// task etc.
}
If you want to be able to cancel tasks you sheduled, you should take a look at NSOperation (which ist based on GCD and allows cancelling and other higher level stuff).
If I am understanding this question correctly, I would first add the observer to the view controller that you want to run the task's completion block
NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("runCompletionBlock"), object: nil)
then in the method you called from the NSNotification you add the completion block.
then create a timer in the class of the hub you want to make
var timer = Timer()
initialise it
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true)
Then create a method runTimedCode() that gets called when the timer finishes.
You can then add a NSNotification to notify the observer you created with
NotificationCenter.default.post(name: Notification.Name("runCompletionBlock"), object: nil)
Not sure if this is what you were after, but it may help.

Change vibrate duration on iPhone's without haptic feedback in 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).")
}