Detect "screenshot" press while in background on Apple Watch - swift

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!

Related

How to update MPNowPlayingInfoCenter/MPRemoteCommandCenter play/pause controls with AVAudioPlayerNode?

I am making a simple music player app that just plays audio using AVAudioEngine. When I pause the AVAudioPlayerNode, it does not update the play/pause control of the MPNowPlayingInfoCenter/MPRemoteCommandCenter. How do I update it?
I dont want to pause the entire AVAudioEngine and then resume to update the MPNowPlayingInfoCenter/MPRemoteCommandCenter play/pause control since it lags up my UI and there's a delay when playing back audio.
Does anyone have any idea or solution regarding this topic? The documentation is absolutely atrocious and no one knows a goddamn thing about AVAudioEngine. Anyone?
func pause() {
playerNode.pause()
audioEngine.pause()
displayLink?.isPaused = true
DispatchQueue.main.async {
self.musicPlayerControlsManager.isPlaying = false
}
}
func playPlayerNode() {
if !audioEngine.attachedNodes.isEmpty && audioFile != nil {
DispatchQueue.main.async {
self.musicPlayerControlsManager.isPlaying = true
self.displayLink?.isPaused = false
}
do {
try audioEngine.start()
playerNode.play()
} catch {
print(error.localizedDescription)
}
}
}

Get Notified When User Starts to Drive (CoreMotion)

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?

Launch Main app from MenuBar app

I've managed to create an Application and I don't want it to be on the Dock until a user clicks the MenuBar Item, then it launches the app and if a user quits the Main app, the menubar Item still remains.
Ok after days of research, I finally figured how to do it.
Swift 2.3
func toggleApp(sender: AnyObject) {
if self.window!.visible {
self.window.orderOut(window)
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Accessory)
} else {
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Regular)
self.window!.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
}
And this is for keeping the Menubar when the user quits(CMD Q) the app:
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
// Cancel terminate if pref set
self.window.close()
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Accessory)
return NSApplicationTerminateReply.TerminateCancel
}
Hope it helps someone.
in Swift 5:
func toggleApp(sender: AnyObject) {
if self.window.visible {
self.window.orderOut(window)
NSApplication.shared.setActivationPolicy(NSApplication.ActivationPolicy.accessory)
} else {
NSApplication.shared.setActivationPolicy(NSApplication.ActivationPolicy.regular)
self.window!.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
}

xcode swift 3 lock screen remote control not working?

i am trying to catch play/stop/next/prev user action from lock screen when player is active and playing , for some how its not working .
inside class MusicPlayerViewController: BaseViewController
override func viewDidLoad() {
super.viewDidLoad()
do {
UIApplication.shared.beginReceivingRemoteControlEvents()
print("bb> Receiving remote control events\n")
} catch {
print("bb> Audio Session error.\n")
}
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget(self, action: #selector(MusicPlayerViewController.nextTrackCommandSelector))
}
func nextTrackCommandSelector()
{
print("omg")
}
in the log i can see only
bb> Receiving remote control events
also inside AppDelegate.swift has
override func remoteControlReceived(with event: UIEvent?) {
print("remote::")
guard let event = event else {
print("no event\n")
return
}
guard event.type == UIEventType.remoteControl else {
print("received other event type\n")
return
}
switch event.subtype {
case UIEventSubtype.remoteControlPlay:
print("received remote play\n")
case UIEventSubtype.remoteControlPause:
print("received remote pause\n")
case UIEventSubtype.remoteControlTogglePlayPause:
print("received toggle\n")
case UIEventSubtype.remoteControlNextTrack:
print("clicked next \n")
case UIEventSubtype.remoteControlPreviousTrack:
print("clicked Prev \n")
default:
print("received \(event.subtype) which we did not process\n")
}
}
and capabilities
what did i miss ?
A few things:
You're using both the delegate style and MPRemoteCommandCenter style of remote event handling. Pick one, rather than both to see if they're conflicting. Apple recommends the MPRemoteCommandCenter style, but if you're supporting older iOS versions, you may need to stick with the delegate style.
If you do elect to use the delegate style, my recollection is that you must also become the first responder in order to begin receiving remote control events.
Regardless of which style of event handling you choose, you must play audio in your app to let the system know to route events to you. The lock screen (or audio control center) should have your app listed in the "Now Playing" area.
i found the solution
for swift 3 i have to add this
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: [])
try! AVAudioSession.sharedInstance().setActive(true)

UI Testing Failure when displaying UIAlertController with no buttons

We're using a UIAlertController as a loading indicator while a network request occurs. There are no actions associated with this UIAlertController as it is closed automatically when the network activity is completed. We display this alert after the user taps the login button for our app.
When we run our tests, they fail at the point right after this with:
UI Testing Failure - Did not receive view did disappear notification within 2.0s
Per other answers on SO, I've tried to use addUIInterruptionMonitor to handle the alert, but without any success. I think this is because there are no actionable buttons on the UIAlertController. Since there's no action that can be taken on the alert, the interruption monitor just looks like this:
addUIInterruptionMonitor(withDescription: "Loading") { handler in
return true
}
Even with this though, I get the same error. How can I work around this?
EDIT: Relevant UI testing code below:
class UI_Tests: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = true
XCUIApplication().launch()
}
func testLogin() {
let app = XCUIApplication()
let tablesQuery = app.tables
let secureTextField = tablesQuery.cells.containing(.staticText, identifier:"PIN").children(matching: .secureTextField).element
secureTextField.tap()
secureTextField.typeText("1234")
app.buttons["Login"].tap()
addUIInterruptionMonitor(withDescription: "Loading") { handler in
return true
}
// Test failure occurs here.
let searchField = tablesQuery.searchFields.element(boundBy: 0)
searchField.tap()
searchField.typeText("hey")
}
}
After reaching out to Apple DTS, it turns out that when displaying a UI interruption / UIAlertController that dismisses based on a timeout, that you need to combine the UI interruption monitor with a timeout-based expectation (otherwise, the interruption monitor will return before the alert has dismissed!).
Using the UI testing example in the question, this approach looks like this:
class UI_Tests: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = true
XCUIApplication().launch()
}
func testLogin() {
let app = XCUIApplication()
let tablesQuery = app.tables
let secureTextField = tablesQuery.cells.containing(.staticText, identifier:"PIN").children(matching: .secureTextField).element
secureTextField.tap()
secureTextField.typeText("1234")
app.buttons["Login"].tap()
addUIInterruptionMonitor(withDescription: "Loading") { alert in
self.expectation(for: NSPredicate(format: "exists == false"), evaluatedWith: alert, handler: nil);
self.waitForExpectations(timeout: 10, handler: nil)
return true
}
// Test failure occurs here.
let searchField = tablesQuery.searchFields.element(boundBy: 0)
searchField.tap()
searchField.typeText("hey")
}
}
This expectation will wait for 10 seconds to be filled. If the alert doesn't dismiss after 10 seconds, the expectation won't be met and the test will fail, but if it does, the test will succeed.