WatchOS: getting applicationDidBecomeActive notifications - swift

I'm making a framework that needs to do stuff when my apple watch is entering background and foreground.
I'm looking for an equivalent of this iOS code for the Apple watch since UIApplication is not present in UIKit anymore :
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
any help would be nice

As of watchOS 7 the following have been added:
WKExtension.applicationDidBecomeActiveNotification
WKExtension.applicationDidEnterBackgroundNotification
WKExtension.applicationDidFinishLaunchingNotification
WKExtension.applicationWillEnterForegroundNotification
WKExtension.applicationWillResignActiveNotification
Source: https://developer.apple.com/documentation/WatchKit/wkextension

Appears that the WatchOS equivalent of
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
is simply
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "applicationDidEnterBackground", name: "UIApplicationDidEnterBackgroundNotification", object: nil)
One just need to replace the emum by its string equivalent

Closest you get is applicationDidBecomeActive and applicationWillResignActive
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
}
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
}
}

Related

ATTrackingManager stopped working in iOS 15

ATTrackingManager.requestTrackingAuthorization stopped working on ios 15. Application rejected from Apple.
According to the discussion in Apple Developer Forum, you need to add delay for about one second when calling requestTrackingAuthorization.
https://developer.apple.com/forums/thread/690607
Example:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
P.S.
Also if you have requesting push notification permission, firstly you need request push notification then request tracking authorization with a delay =>
private func requestPushNotificationPermission() {
let center = UNUserNotificationCenter.current()
UNUserNotificationCenter.current().delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge], completionHandler: { (granted, error) in
if #available(iOS 14.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
}})
UIApplication.shared.registerForRemoteNotifications()
}
The problem has been solved, just call it in applicationDidBecomeActive:
https://developer.apple.com/forums/thread/690762
Follow by apple doc:
Calls to the API only prompt when the application state is UIApplicationStateActive.
So, we need to call ATTrackingManager.requestTrackingAuthorization on
applicationDidBecomeActive of AppDelegate.
But If you're using scenes (see Scenes), UIKit will not call this method. Use sceneDidBecomeActive(_:) instead to restart any tasks or refresh your app’s user interface. UIKit posts a didBecomeActiveNotification regardless of whether your app uses scenes.
So, my approach is to register on addObserver on didFinishLaunchingWithOptions such as:
NotificationCenter.default.addObserver(self, selector: #selector(handleRequestEvent), name: UIApplication.didBecomeActiveNotification, object: nil)
on handleRequestEvent:
requestPermission() // func call ATTrackingManager.requestTrackingAuthorization NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
Hope this helps. It's work for me.
Make sure your iPhone's Settings -> Privacy -> Tracking is enabled. Otherwise, it won't prompt for request Aurthorization.

Creating an observer to check if MediaPlayer playbackState is paused or not

I have a music app and I wish to determine if playback has been paused while the app was closed (due to an event like a phone call or AirPods being taken out of ear etc)
My first approach was to run a func inside of viewWillAppear that checked
if mediaPlayer.playbackState == .paused {
...
}
If it was paused I updated the play/pause button image. However, this did not work, the play/pause button would still show Play even if it was paused.
Next, I tried adding an observer to the viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(self.wasSongInterupted(_:)), name: UIApplication.didBecomeActiveNotification, object: self.mediaPlayer)
The self.wasSongInterupted I call is
#objc func wasSongInterupted(_ notification: Notification) {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .paused {
print("paused")
self.isPlaying = false
self.playPauseSongButton.isSelected = self.isPlaying
} else if self.mediaPlayer.playbackState == .playing {
self.isPlaying = true
self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
However, I am still having the same issue.
What is the best way to determine if my music player is playing or paused when I reopen the app?
Edit 1: I Edited my code based on comments.
wasSongInterrupted was not being called, and through breakpoints and errors I discovered the code was mostly not needed. I changed my code to be
func wasSongInterrupted() {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .interrupted {
var isPlaying: Bool { return self.mediaPlayer.playbackState == .playing }
print("Playback state is \(self.mediaPlayer.playbackState.rawValue), self.isPlaying Bool is \(self.isPlaying)")
self.playPauseSongButton.setImage(UIImage(named: "playIconLight"), for: .normal)
//self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
and inside my AppDelegate's applicationDidBecomeActive I have
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
Now the code runs, however I have an issue.
If I run the following code:
if self.mediaPlayer.playbackState == .interrupted {
print("interrupted \(self.isPlaying)")
}
and then make a call and come back to the app it will hit the breakpoint. It will print out interrupted as well as false which is the Bool value for self.isPlaying
However if I try to update the UI by
self.playPauseSongButton.isSelected = self.isPlaying
or by
self.playPauseSongButton.setImage(UIImage(named: "playIconLight.png"), for: .normal)
I get an error message Thread 1: EXC_BREAKPOINT (code=1, subcode=0x104af9258)
You trying to update you player UI from viewWillAppear. From Apple Documentation:
viewWillAppear(_:)
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view.
So if your app was suspended and the becomes active again, this method won't be called, because your UIViewController is already at Navigations Stack.
If you want to catch the moment when your app becomes active from suspended state, you need to use AppDelegate. From Apple Documentation:
applicationDidBecomeActive(_:)
This method is called to let your app know that it moved from the inactive to active state. This can occur because your app was launched by the user or the system.
So you need to use this method at your AppDelegate to handle app running and update your interface.
UPDATE
You saying the inside this AppDelegate method you're doing
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
That's wrong because you're creating a new view controller. What you need to do, is to access you existing view controller from navigation stack and update it.
One of the possible solutions is to use NotificationCenter to send a notification. You view controller should be subscribed to this event of course.
At first, you need to create a notification name
extension Notification.Name {
static let appBecameActive = Notification.Name(rawValue: "appBecameActive")
}
Then in you AppDelegate add following code to post your notifications when app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
NotificationCenter.default.post(name: .appBecameActive, object: nil)
}
And finally in your view controller add to subscribe it on notifications
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(wakeUp),
name: .appBecameActive,
object: nil)
...
}
#objc func wakeUp() {
// Update your UI from here
}
Hope it helps you.

Observing Notification in Today Widget which is posted from main app

I have declared notification name in a swift file which has target membership of both main app and today widget:
let SpecialKey = Notification.Name("howLongNotif")
Then in my main app view controller, I am posting notification when location is updated (background mode for location updates is on):
NotificationCenter.default.post(name: SpecialKey, object: nil, userInfo: nil)
In my today widget viewDidLoad, I am observing it like this:
NotificationCenter.default.addObserver(self, selector: #selector(TodayViewController.dataReceived), name: SpecialKey, object: nil)
and have:
func dataReceived(_notification: NSNotification) {
print("data received")
}
But dataReceived function is never invoked.
I tested by moving the observer and dataReceived function to main app and it works fine there.
Your main app and today is extension are run as separate processes on the phone. NSNotificationCenter only works within a single process.
To pass information between your extension and main app, you can use NSUserDefaults or a file in the shared container.

how to detect tap home-button twice in ios9

In my app which am writing to learn swift and iOS9, I'm trying to pause my NStimer when user double click the home button and becomes at app switcher, accoridng to programming ios9 matt neuberg, when The user double-clicks the Home button, The user can now work in the app switcher interface. If your app is frontmost, your app delegate receives this message:
applicationWillResignActive:
But my timer only pauses when I tap home button once and when I tap twice and have the app switcher, I see my timer counting, any ideas?
Try to add this lines in your AppDelegate.swift:
static let kAppDidBecomeActive = "kAppDidBecomeActive"
static let kAppWillResignActive = "kAppWillResignActive"
func applicationDidBecomeActive(application: UIApplication) {
// Your application is now the active one
// Take into account that this method will be called when your application is launched and your timer may not initialized yet
NSNotificationCenter.defaultCenter().postNotificationName("kAppDidBecomeActive", object: nil)
}
func applicationWillResignActive(application: UIApplication) {
// Home button is pressed twice
NSNotificationCenter.defaultCenter().postNotificationName("kAppWillResignActive", object: nil)
}
In addition, set your view controller as the observer to those notifications:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(pauseGame), name: AppDelegate.kAppWillResignActive, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(resumeGame), name: AppDelegate.AppDelegate.kAppDidBecomeActive, object: nil)
}

Call function on app termination in Swift

How can I call a function that is within an SKScene class when my app is terminated by the user?
I need to modify a value and save it to NSUserDefauts when the app is terminated.
You can register to receive a notification when your app is about to terminate. To do this, add an observer to the default notification center by
Swift 5:
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: UIApplication.willTerminateNotification, object: nil)
Swift 3/4:
// Add this to didMoveToView in your SKScene subclass
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
Add the following method to your SKScene subclass. The method will be called before the app terminates. It must be "exposed" to Objective-C by adding #objc so the notifier can use #selector().
#objc func saveData(notification:NSNotification) {
// Save your data here
print("Saving data...")
}
In Swift 3 and 4 you have something like that:
in your viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(toDoSomething), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
and than you have that method to be called
func suspending () {
print("toDoSomething...")
}
There are a few methods in UIAppDelegate that will help you. Take a look at applicationWillTerminate(_:) and applicationWillResignActive(_:). From there you see what state your app is in and do perform the appropriate actions.