Calling native macOS APIs

You can call into Cocoa APIs to use methods and properties that are not built into the framework by using the Declare command. To create a Declare statement you first need to track down the API you want to use using Apple's documentation: Apple Developer Documentation. Most of the time you will reference the Foundation and AppKit libraries, but there are many other libraries as well. Xojo Declares use the Objective-C names so be sure to refer to those in the documentation rather than the Swift naming.

When you call Cocoa methods you supply the method name using the Selector part of the Declare command. The selector name has to end in a ":" if there are any parameters that are passed, an oddity of how Objective-C works. Unlike with Xojo methods, the case of the name also has to match exactly. For the Xojo Declare, the first parameter is always required and must be a reference to the class containing the method you are calling.

Center a window

To start with a simple example, consider that you may to center a window on the screen. You can obviously do this by manually adjusting the window's Top and Left properties by taking into account the window size and the screen size, but Cocoa has a simple center function that can be used on a window.

On macOS, a Xojo window is actually a native NSWindow. When you view Apple's docs for NSWindow you'll see there is a center method. This method is very simple as it does not take any parameters. Looking at the doc page you should note that this function is in the AppKit library. Now you can create a Declare command to map to the center method:

Declare Sub centerWindow Lib "AppKit" Selector "center" (windowHandle As Ptr)

Remember, even though the center method does not take any parameters, you still have to add a parameter to the Declare so you can pass in the reference to the calling class, which in this case is the window to center.

You can call this method like this (such as from a button's Pressed event handler):

centerWindow(Self.Handle)

Because this method is called for a window, you can put it in a Xojo Extension Method to make it easier to call. To do this, create a global method on a module like this:

Public Sub Center(Extends w As DesktopWindow)
  #If TargetMacOS Then
    Declare Sub centerWindow Lib "AppKit" Selector "center" (windowHandle As Ptr)
    centerWindow(w.Handle)
  #EndIf
End Sub

Note the use of the "#If TargetMacOS" line. This prevents this code from being compiled into Windows or Linux builds of your app where the code could possible crash your app.

This now allows you to have code like this on a window's Opening event to center the window:

Self.Center

Display the standard About window

Instead of manually creating a modal window to display the About window for your app, you can instead display the standard macOS About Window. This window has a fixed size, gets the app name directly from the built app name, the icon from the app icon and the version from the version values in Shared Build Settings. On the NSApplication class there is a method called orderFrontStandardAboutPanel that displays this standard about window.

The first thing you need to do is to get a reference to the shared application. Xojo does not provide this for you so you'll have to use some Declares to get at it.

To get started, in Xojo create a public method on the App object and call it ShowAbout.

The first bit of code you need has to get a reference to the NSApplication class and then call the sharedApplication method to get an instance of the app. In order to do this, you'll need to use a common utility function, NSClassFromString, that gets a reference to a Cocoa class. So this is the first line of code:

Declare Function NSClassFromString Lib "Foundation" (className As CFStringRef) As Ptr

You can now use this function to get a reference to the NSApplication class:

Var nsApp As Ptr = NSClassFromString("NSApplication")

Next you can use this to call the sharedApplication method on NSApplication with this Declare:

Declare Function SharedApplication Lib "Foundation" Selector "sharedApplication" (receiver As Ptr) As Ptr
Var sharedApp As Ptr = SharedApplication(nsApp)

You now have the reference to the app in the sharedApp variable. The last thing to do is to call the orderFrontStandardAboutPanel method, which has this Objective-C declaration in the Apple docs:

(void)orderFrontStandardAboutPanel:(id)sender;

This tells you it is a Sub (the void at the beginning means it does not return a value) and that it takes one parameter. So the Declare looks like this:

Declare Sub OrderFrontStandardAboutPanel Lib "AppKit" Selector "orderFrontStandardAboutPanel:" (sharedApp As Ptr, ID As Ptr)

And lastly you can call this method like this (a value for the ID parameter is not actually needed):

OrderFrontStandardAboutPanel(sharedApp, Nil)

Combining all this code together results in this:

Public Sub ShowAbout()
  #If TargetMacOS Then
    Declare Function NSClassFromString Lib "Foundation" (className As CFStringRef) As Ptr
    Var nsApp As Ptr = NSClassFromString("NSApplication")

    Declare Function SharedApplication Lib "Foundation" Selector "sharedApplication" (receiver As Ptr) As Ptr
    Var sharedApp As Ptr = SharedApplication(nsApp)

    Declare Sub OrderFrontStandardAboutPanel Lib "AppKit" Selector "orderFrontStandardAboutPanel:" (sharedApp As Ptr, ID As Ptr)
    OrderFrontStandardAboutPanel(sharedApp, Nil)
  #EndIf
End Sub

As above, note the use of the "#If TargetMacOS" line which prevents this code from being compiled into Windows or Linux builds of your app where the code could possibly crash your app.

You can now display the about window with this code:

App.ShowAbout

64-bit information

Some API calls need to adapt when used in a 64-bit app. For example, some API calls expect an Int32 in an 32-bit app but want an Int64 in a 64-bit app. If you have hard-coded usage of Int32 in your Declare API calls then you'll have to fix them so that they work for 64-bit. For Int32/Int64 you should just use the Integer type which will automatically use the correct type for the type of the app.

You may run into a similar issue with floating point where Single is used in 32-bit apps, but Double is used for 64-bit apps. In these cases use the CGFloat type to have it automatically choose the correct type.