Get Notified When User Starts to Drive (CoreMotion) - swift

I would like to send a notification to the user whenever he/she starts driving using CoreMotion. I can use CoreMotion to see what the user is doing while my app is on like so...
let activityManager = CMMotionActivityManager()
override func viewDidLoad() {
super.viewDidLoad()
activityManager.startActivityUpdates(to: .main) { (activity) in
guard let activity = activity else {
return
}
if activity.automotive {
print("Driving")
}
if activity.stationary {
print("Not Moving")
}
}
}
}
But how would I be able to detect the change to activity.automotive in the background to send a notification to the user even if my app is not on?

Related

How to set NowPlaying properties with a AVQueuePlayer in Swift?

I have an AVQueuePlayer that gets songs from a Firebase Storage via their URL and plays them in sequence.
static func playQueue() {
for song in songs {
guard let url = song.url else { return }
lofiSongs.append(AVPlayerItem(url: url))
}
if queuePlayer == nil {
queuePlayer = AVQueuePlayer(items: lofiSongs)
} else {
queuePlayer?.removeAllItems()
lofiSongs.forEach { queuePlayer?.insert($0, after: nil) }
}
queuePlayer?.seek(to: .zero) // In case we added items back in
queuePlayer?.play()
}
And this works great.
I can also make the lock screen controls appear and use the play pause button like this:
private static func setRemoteControlActions() {
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { [self] event in
queuePlayer?.play()
return .success
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { [self] event in
if queuePlayer?.rate == 1.0 {
queuePlayer?.pause()
return .success
}
return .commandFailed
}
}
The problem comes with setting the metadata of the player (name, image, etc).
I know it can be done once by setting MPMediaItemPropertyTitle and MPMediaItemArtwork, but how would I change it when the next track loads?
I'm not sure if my approach works for AVQueueplayer, but for playing live streams with AVPlayer you can "listen" to metadata receiving.
extension ViewController: AVPlayerItemMetadataOutputPushDelegate {
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
//look for metadata in groups
}
}
I added the AVPlayerItemMetadataOutputPushDelegate via an extension to my ViewController.
I also found this post.
I hope this gives you a lead to a solution. As said I'm not sure how this works with AVQueuePlayer.

Detect "screenshot" press while in background on Apple Watch

I have followed this answer to make my app detect when the crown and side button are pressed at the same time. This works for me, but only when the app is in the foreground. My app uses an HKWorkoutSession to stay running in the background, but it does not detect the press of the two buttons. I need the app to detect the pressing of both buttons in the background for my app to work properly.
My code for setting up the workout session is:
func configureWorkout() {
configuration.activityType = .walking
configuration.locationType = .unknown
do {
session = try HKWorkoutSession(healthStore: store, configuration: configuration)
session!.delegate = self
session!.startActivity(with: dateManager.getDate())
}
catch let error as NSError {
fatalError("*** Unable to create the workout session: \(error.localizedDescription) ***")
}
}
The code I have so far to detect the buttons being pressed is:
func workoutSession(_ workoutSession: HKWorkoutSession, didGenerate event: HKWorkoutEvent) {
if(event.type == HKWorkoutEventType.pauseOrResumeRequest) {
print("Detected Press")
}
}
Thanks!

Getting access to EKCalendar

In my app, I have a switch that allows the user to put certain events in their agenda. I handle that as such:
#IBAction func putInAgenda(_ sender: UISwitch) {
let store = manager.store
if (sender.isOn){
store.requestAccess(to: EKEntityType.event, completion: {
(accessGranted: Bool, error: Error?) in
if accessGranted == true {
self.eventsHandler.importEventsInAgenda(id)
} else {
DispatchQueue.main.async {
sender.isOn = false
}
}
})
} else {
//
}
shared?.set(sender.isOn, forKey: "putInAgenda")
shared?.synchronize()
}
However, against my expectation, "store.requestAccess" not only requests, but also SETS.
As a result, when the user CANCELS the dialog, the switch switches back (expected) but any consecutive attempt to switch the switch to the ON position is honored with an OFF position, without a new dialog.
What should I do?
A privacy request is only ever asked once. If you detect that it is currently denied, you could either update the UI or prompt the user to go to Settings and turn it on. You can use UIApplication openSettingsURLString and UIApplication openURL to take the user to your app's settings page in the Settings app.

Firebase Login Persistence Swift

I'm using Firebase to handle my user register and login for my app. But if I log in, and then close my app entirely - the user is forced to re-log in. I'd like to keep the user logged in unless they click "Log out"
My login code is this:
Auth.auth().signIn(withEmail: email, password: password, completion: {(user, error) in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
}
self.presentTabBar()
})
}
}
How do I keep this user logged in unless specifically told to logout?
Here's a handy full example for 2020:
Anywhere in your iOS+Firebase app, you can simply say:
guard let uid = Auth.auth().currentUser?.uid else {
return print("no current user!")
}
Thus, on the launch screen of your app, simply:
import UIKit
import Firebase
import FirebaseUI
class ViewController: UIViewController, FUIAuthDelegate {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let uid = Auth.auth().currentUser?.uid else {
return print("no current user, hence AuthUI flow...")
basicAuthUIFlow()
}
print("User .. \(Auth.auth().currentUser?.displayName)")
continueWhenUserPresent()
}
It's that easy. So then as usual ...
private func basicAuthUIFlow() {
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
let pp: [FUIAuthProvider] = [ FUIGoogleAuth() ]
authUI?.providers = pp
if let authVC = authUI?.authViewController() {
present(authVC, animated: true)
}
}
func authUI(_ authUI: FUIAuth,
didSignInWith authDataResult: AuthDataResult?,url: URL?, error: Error?) {
let u = authDataResult?.user
print("Successful login via authUI \(String(describing: u?.displayName))")
continueWhenUserPresent()
}
private func continueWhenUserPresent() {
.. pushViewController .. your first screen
}
Check if the user is logged in or not:
if Auth.auth().currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
if the user is logged in, then go the Home ViewController. This way when he opens the app again he will go to the Home ViewController unless he sign outsFIRAuth.auth().signOut()
For more info check this: https://firebase.google.com/docs/auth/ios/manage-users
For keep user login you need to check currentUser Auth session, If it's not nil then you can redirect user to Home screen.
Call "setRootViewController" method from didFinishLaunchingWithOptions, just after FirebaseApp.configure() code
Swift 4
func setRootViewController() {
if Auth.auth().currentUser != nil {
// Set Your home view controller Here as root View Controller
self.presentTabBar()
} else {
// Set you login view controller here as root view controller
}
}

How to open a watch app from parent iOS app?

I need to open my watch extension from my parent iOS app.
I have seen a similar feature implemented in Nike+ Run Club app. ie, When the User taps on Start button in Parent app will open the watch kit extension instantly.
As #abjurato said, you can only launch it in a "workout mode"
import HealthKit
import WatchConnectivity
let healthStore = HKHealthStore()
func startWatchApp() {
print("method called to open app ")
getActiveWCSession { (wcSession) in
print(wcSession.isComplicationEnabled, wcSession.isPaired)
if wcSession.activationState == .activated && wcSession.isWatchAppInstalled {
print("starting watch app")
self.healthStore.startWatchApp(with: self.configuration, completion: { (success, error) in
// Handle errors
})
}
else{
print("watch not active or not installed")
}
}
}
func getActiveWCSession(completion: #escaping (WCSession)->Void) {
guard WCSession.isSupported() else { return }
let wcSession = WCSession.default()
wcSession.delegate = self
if wcSession.activationState == .activated {
completion(wcSession)
} else {
wcSession.activate()
wcSessionActivationCompletion = completion
}
}