How to debug an app activated from dock menu - swift

I'm trying to understand how to debug a dock open document event.
I'd like to understand the environment so that normal document restoration can be disabled on startup when the app is stated to open some document, especially when the doc itself is a candidate for restoration.
But, since the app is seemingly started, "no debug", breaks in several app delegate methods are never called; i.e.,
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool
func applicationDockMenu(sender: NSApplication) -> NSMenu?
As each document open event will instantiate a document window, normally I'd like to avoid that but having so is useful, so I'd like the ability to debug it while in this sort of start up, so long as I'm attached to the debugger.

If you want to debug the app launch process while the app is not connected to a debugger, you may need to just rely on unified logging. For example, I often use this pattern, with a private OSLog for each source file, which I can then observe these log statements in the Console:
// ViewController.swift
import Cocoa
import os.log
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ViewController")
class ViewController: NSViewController {
var string = "..."
override func viewDidLoad() {
super.viewDidLoad()
os_log(.debug, log: log, "viewDidLoad: string = %{public}#", string)
}
...
}
Then, if the Console is configured to show debugging statements (e.g. “Action” » “Include Debug Messages”), I can filter for these (e.g. type subsystem: in the search bar, followed by your subsystem, the bundle identifier in this case). Then, completely independent of Xcode, I can watch these log statements from the Console app (and filter for my subsystem and/or category) as my app runs, not attached to the debugger:
This technique works for both macOS and iOS apps.
For more information, see the Unified Logging and Activity Tracing video.
Obviously, once your debug build is running, but having been launched independently of the debugger, you can manually attach Xcode’s debugger to it via the “Debug” » “Attach to process” menu option.
But attaching a debugger to an app that is already running is not practical when you’re trying to debug the startup sequence when an app launches. But the aforementioned unified logging is often sufficient for confirming what methods were called when, through the judicious use of os_log statements.

Related

macOS Catalyst configurationForConnecting UISceneSession delegate function not always called on app launch

I'm building a macOS Catalyst app with support for multiple windows, which is implemented with the new UISceneDelegate set of APIs introduced in iOS 13.
According to Apple's documentation, as a new window is created, a scene delegate needs to connect to UISceneSession, which allows passing information to this session via its userInfo property. One way to set userInfo before the scene delegate is connected is in this function of UISceneDelegate:
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
connectingSceneSession.userInfo?["foo"] = "bar"
return UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
Looking at the Developer Reference page for this function, it seems it should be always called on app launch before the main app's scene is set up. Unfortunately, it seems with macOS Catalyst this is not true, but I'm not able to find any documentation or logic for why this function is not always called. In my application, which reuses trivial sample code for UISceneDelegate, this function is called randomly about 50% of the time on app launch. This is not great, as it means that randomly 50% of the time app's windows are not properly set up, as required information is not passed in the userInfo property of UISceneSession.
What is the exact logic for calls to application(_:configurationForConnecting:options:) or how to enforce that it is called deterministically on app launch for the first app's scene with macOS Catalyst?
This isn't specific to Catalyst - it happens in iOS too. It's to do with window restoration versus creation. To understand that, see my answer to this more generic question: Why is UIApplicationDelegate method `application(_:configurationForConnecting:options:)` not called reliably
It could not be called even in iOS, not only Mac Catalyst
Once you have configured UISceneSession with particular persistentIdentifier UIKit will not call application(_:configurationForConnecting:options:) for the session with this persistentIdentifier again.
UIKit calls this method shortly before creating a new scene

Accessibility Permissions for Development in macOS and XCode

Is there a way to give an app I am developing in XCode accessibility permissions by default during development. The idea is that I could hit the run key and test the new code without having to jump through the hoops in the settings. For deployment obviously it wouldn't work, but for development is there a way to basically whitelist the app?
EDIT: This is a new method I've found/created that is working and has the added benefit of prompting the user first too, improving the onboarding experience of your app. The original method (which I've left at the bottom of this post) still prompted for accessibility on each new build of the app unfortunately.
I had to get this working in my macOS app and had another idea and way to approach it, given the user needs to action it anyway, I just have the app present the required dialogue on each run and during the app build, I run a script to clear the existing permissions.
For reference, see v1.3.0 of my Auto Clicker macOS app, specifically:
Services/PermissionsService.swift (whole class)
Init/AppDelegate.swift (line 21)
Init/AutoClickerApp.swift (line 32 & 40)
auto-clicker.xcodeproj/project.pbxproj (line 435)
The links are links to the tagged version, so should never change or break. There is quite a bit of code, I'll try to summarise here.
Firstly, I created a new class to manage permissions related functionality to keep things contextual:
//
// PermissionsService.swift
// auto-clicker
//
// Created by Ben on 10/04/2022.
//
import Cocoa
final class PermissionsService: ObservableObject {
// Store the active trust state of the app.
#Published var isTrusted: Bool = AXIsProcessTrusted()
// Poll the accessibility state every 1 second to check
// and update the trust status.
func pollAccessibilityPrivileges() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.isTrusted = AXIsProcessTrusted()
if !self.isTrusted {
self.pollAccessibilityPrivileges()
}
}
}
// Request accessibility permissions, this should prompt
// macOS to open and present the required dialogue open
// to the correct page for the user to just hit the add
// button.
static func acquireAccessibilityPrivileges() {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true]
let enabled = AXIsProcessTrustedWithOptions(options)
}
}
Then, I added the following to my AppDelegate (baring in mind this was in Swift UI):
//
// AppDelegate.swift
// auto-clicker
//
// Created by Ben on 30/03/2022.
//
import Foundation
import Cocoa
final class AppDelegate: NSObject, NSApplicationDelegate {
// When the application finishes launching, request the
// accessibility permissions from the service class we
// made earlier.
func applicationDidFinishLaunching(_ notification: Notification) {
PermissionsService.acquireAccessibilityPrivileges()
}
}
Then finally, in our Swift UI app init, lets add some UI feedback to the user that we are waiting for permissions and listen to that isTrusted published property we setup earlier in the permissions service class that gets polled every second to unlock the UI when the user has granted the required permissions:
//
// AutoClickerApp.swift
// auto-clicker
//
// Created by Ben on 12/05/2021.
//
import Foundation
import SwiftUI
import Defaults
#main
struct AutoClickerApp: App {
// Create an instance of the permissions service class that we
// can observe for the trusted state change.
#StateObject private var permissionsService = PermissionsService()
var body: some Scene {
// Wait for trust permissions being granted from the user,
// displaying a blocking permissions view telling the user
// what to do and then presenting the main application view
// automatically when the required trust permissions are granted.
WindowGroup {
if self.permissionsService.isTrusted {
MainView()
} else {
PermissionsView()
}
.onAppear(perform: self.permissionsService.pollAccessibilityPrivileges)
}
}
}
You can see the blocking view I made in the app above, Views/Main/PermissionsView.swift.
Then in order to automatically clear the permissions during the app build, I added a new build script to the project that runs the following against /bin/sh:
tccutil reset Accessibility $PRODUCT_BUNDLE_IDENTIFIER
As seen in auto-clicker.xcodeproj/project.pbxproj (line 435).
This means I'll be prompted with the system dialogue on each app build to just press the + button and add the app.
Unfortunately this is the most frictionless way I've found to develop the app with these permissions being required.
Outdated answer:
Found a way to do this after some trial and error, navigating through Xcode's (>11, currently 13) new build system.
With Xcode open and having it as the foreground app (so it takes over the menu bar with its menu items), do the following:
From the menu bar, select 'Xcode'
If your project doesn't yet have a workspace, click 'Save as Workspace...' near the bottom of the list and save the Workspace alongside your *.xcodeproj so they should be in the same directory. From now on you'll open your project via the new *.xcworkspace Workspace instead of your *.xcodeproj Project.
From the menu bar, select 'Xcode' again
Click 'Workspace Settings...' near the bottom of the list
Under 'Derived Data' select 'Workspace-relative Location', if you want to customise the path do so now
Click 'Done' in the bottom right
This puts the build location of our *.app binary within the project so its easy to find, along with also allowing us to check the change into source control as we now have the Workspace settings stored in the *.xcworkspace file.
Next we need to now point the permissions at the above build binaries location, so:
Open System Preferences
Click 'Security & Privacy'
Click the padlock in the bottom right to make changes
Select 'Accessibility' from the left side list
Click the plus button at the bottom left of the list and find the *.app file to add it to the list that we put within the project directory, this should be something like $PROJECT_DIR/DerivedData/$PROJECT/Build/Products/Debug/*.app
Click the checkbox to the left of the app to check it if its not already checked
Restart the app
Any builds should now have the relevant permissions.
Just to note though, this will overwrite the permissions of any archived/prod builds as the app names and binaries are the same. They are easily added back though, just delete the permission for the build app and instead point it back at your prod app, usually in /Applications.

print not working in Swift 3 extensions

I'm new at Swift 3 and I try to make a print("Test") in a Widget extension.
I tried the same code in ViewController.swift and It works ok. I don't know why it works there but it doesn't on TodayViewController.swift. I can't access to objects too.
func loadData() {
let query = PFQuery(className: "Noticias")
query.whereKey("titulo", equalTo:"Es Navidad")
query.findObjectsInBackground(block: { (objects : [PFObject]?, error: Error?) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
print(object.objectId!)
}
}
} else {
// Log details of the failure
print("bad day homie")
print(error!)
}
})
}
I attach I picture to see it clearly. If I try to print on the file marked as Work, it works. But if I try it on the file marked ad NO, it doesn't.
It is extremely difficult to retrieve print messages from an extension. The problem is that it's an extension! It isn't running in your app, so it doesn't arrive at your console. Sometimes I find you can solve this problem by switching the debugged process in the Debug Bar at the top of the debug area (at the bottom of the screen, not shown in your screen shot), but at other times this doesn't work.
I'll illustrate a possible technique that seems to be pretty reliable. Look at this screen shot:
"Expand" is an action extension. But my containing app is called "bk2ch13...". So how will I ever manage to pause at the breakpoint shown at the right, which is in the action extension? This is what I do.
First, with the screen as shown above, I build and run my containing app.
Then, I switch the target to the action extension:
Now I build and run again. But now I am trying to run an action extension, which you can't do, so Xcode asks me what app to run:
So I choose "bk2ch13...". So now we are running my host app again, but we are debugging the extension. So I use my host app to exercise the extension, and sure enough, we pause at the breakpoints and print statements arrive into the console.
Note, in that screen shot, how the debug bar clearly shows that we are talking to the extension, not the host app.

OSX/Swift Make Application Useable Anywhere

I have an application written in osx swift, and I need help with a particular behavior/issue. I have the application set to be above all other windows using this code:
self.window!.level = Int(CGWindowLevelForKey(Int32(kCGScreenSaverWindowLevelKey)))
A little issue with it, from what I've seen, is that it does work... but if I go to my launchpad applications menu, the window still shows up. That's exactly how I want the window to behave, but whenever I hit a button within the window, the application resets to the normal desktop to function properly. I have a link below demonstrating this:
https://gyazo.com/f4d05c10ad7b5dbf8b95f3bd2aa23cc4
See how I'm getting taken out of my launchpad and then reset to the normal desktop screen whenever I hit buttons within my application? I just really need to fix this issue.
Is there any info.plist property that I can use to prevent this from happening? Is there any piece of code I could use to make my window use-able everywhere without resetting it to the proper desktop environment? Thanks in advance!
NSFloatingWindowLevel might be a better choice:
import Cocoa
let kCGFloatingWindowLevel: CGWindowLevel =
CGWindowLevelForKey(CGWindowLevelKey.FloatingWindowLevelKey)
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.window!.level = Int(kCGFloatingWindowLevel)
}
}
The window level type you are currently using is primarily meant for screensaver windows. If you want a floating palette or window then using the above should give you the desired behavior.
↳ NSWindow : NSFloatingWindowLevel

NSApplication keyWindow is nil during applicationDidFinishLaunching

Starting with a blank OS X application project, I add the following code to applicationDidFinishLaunching.
func applicationDidFinishLaunching(aNotification: NSNotification) {
let app = NSApplication.sharedApplication()
guard let window = app.keyWindow else {
fatalError("No keyWindow\n")
}
print(window)
}
At launch I hit the error case because my local window variable is nil. Yet when I show the contents of the app variable, I see a valid value for _keyWindow. Also notice that the blank GUI Window is being displayed on the screen next to the stack dump.
Why does the keyWindow: NSWindow? property return nil in this case?
Thanks
NSApplication's keyWindow property will be nil whenever the app is not active, since no window is focused for keyboard events. You can't rely on it being active when it finished launching because the user may have activated some other app between when they launched your and when it finished launching, and Cocoa is designed to not steal focus.
To some extent, you may be seeing that happen more when launching from Xcode because app activation is a bit strange in that case. But, still, you must not write your applicationDidFinishLaunching() method to assume that your app is active.
What you're seeing in terms of the app's _keyWindow instance variable is, of course, an implementation detail. One can't be certain about what it signifies and you definitely should not rely on it. However, I believe it's basically the app's "latent" key window. It's the window that will be made key again when the app is activated (unless it is activated by clicking on another of its windows).