12

I'd like to use Swift to build an OS X Preference Pane plugin for the System Preferences app, but I can't get it to work.

enter image description here

After clicking "Next" the Xcode template doesn't offer an option to choose Swift as a language, but automatically creates the project in Objective-C.

enter image description here

Without adding any code or doing anything else, the project builds successfully. If you right-click on the Product and select "Open in External Editor", System Preferences will successfully install and load the preference pane.

enter image description here

enter image description here

enter image description here

It just works!

Well that's great, but now, I want to add a new Cocoa subclass using Swift.

enter image description here

enter image description here

Accepting the default, and allowing it to create the Bridging Header.

enter image description here

Now, quit System Preferences and without adding any code, rebuild the project. As before, right-click the Product and "Open in External Editor".

System Preferences will confirm replacing the preference pane, and it will install it, but then it fails to load.

enter image description here

enter image description here

If you show the built Product in the Finder, in addition to the .prefPane plugin, there's also a .swiftmodule folder.

enter image description here

I'm guessing there's something missing in the Build Phases or Build Settings that's responsible for incorporating the .swiftmodule with the rest of the bundle, but haven't been able to figure it out.

After you add some code that uses the new class, it's necessary to import the Swift project umbrella header ("Prax-Swift.h") to make the project compile, but importing the umbrella header doesn't fix this problem.

//  Prax.h

#import <PreferencePanes/PreferencePanes.h>
#import "Prax-Swift.h"

@interface Prax : NSPreferencePane

@property PraxObject *ourPrax;

- (void)mainViewDidLoad;

@end

I also tried deleting Prax.h and Prax.m and simply implementing the NSPreferencePane subclass in Swift. As before, the project builds and installs, but System Preferences fails to load it.

//  Prax.swift

import PreferencePanes

class Prax: NSPreferencePane {

    override func mainViewDidLoad() {

    }
}

Sorry if I've used too many pictures in this question; it seemed to be the clearest way to explain the problem and make it easy to reproduce. There's probably a simple solution. Any ideas?

ElmerCat
  • 3,126
  • 1
  • 26
  • 34
  • 1
    Have you looked at the Console application? Chances are that you'll find an error message there. – zneak Aug 17 '15 at 01:57
  • No, there's no error messages showing up in Console. There are System Preferences activity messages, reflecting "Show All" and other preference panes that are visited, but nothing about the one it fails to load. I wouldn't expect it to give much of a clue about the problem, since it seems to be something about Xcode not building the plugin bundle correctly. – ElmerCat Aug 17 '15 at 02:26

2 Answers2

8

First, you need to enable the "Embedded Content Contains Swift" setting so that Xcode will copy the necessary Swift libraries into the bundle.

Then, you get this error:

System Preferences[68872]: dlopen_preflight failed with
  dlopen_preflight(/.../preftest.prefPane/Contents/MacOS/preftest):

  Library not loaded: @rpath/libswiftAppKit.dylib
    Referenced from: /.../preftest.prefPane/Contents/MacOS/preftest  
    Reason: image not found for /.../preftest.prefPane

This means the app doesn't know where to load the included Swift libraries from.

To fix this, add @loader_path/../Frameworks to the runpath search paths in the build settings, telling it that the Swift libraries are in the Frameworks directory of your prefpane:

See the dyld man page for further info about dynamic loading.

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • Does that also require adding a copy files phase so that the Swift library will be in the Frameworks directory? – Tom Harrington Aug 17 '15 at 04:34
  • Cool! All I had to do was turn on the switch for "Embedded Content Contains Swift Code". The runtime search path was already there, and I didn't have to add a copy files phase. Thanks, jtbandes! – ElmerCat Aug 17 '15 at 05:54
  • 1
    Does this still work for Swift 4 and macOS High Sierra? – Klaas Nov 16 '17 at 11:27
  • I’m not sure why it wouldn’t, but I haven’t tried. Are you having trouble? – jtbandes Nov 16 '17 at 16:39
  • 1
    @jtbandes I'm working on a screen saver in Swift. They are also installed into System Preferences and it seems Apple changed the loading of them in High Sierra. See https://github.com/klaas/QlaasSwiftScreenSaver and https://forums.developer.apple.com/message/268934#268934 – Klaas Nov 20 '17 at 11:02
  • 2
    @jtbandes I just created a dummy preferences pane with Swift 4 in Xcode 9.1 and it actually crashes like the screen saver when used under High Sierra. Would love to see s.o. verify my findings. Here is my sample pane: https://github.com/klaas/QlaasSwiftPreferencesPane Will create a radar now. – Klaas Nov 20 '17 at 19:44
  • embedding cocoa and preferencePane frameworks and adding the following user-defined build settings enabled me to run: SWIFT_FORCE_STATIC_LINK_STDLIB = YES SWIFT_FORCE_DYNAMIC_LINK_STDLIB = NO – Dustin Nielson Dec 20 '18 at 21:57
0

There was an Apple bug introduced with macOS High Sierra. This bug is now resolved in the latest dot release of macOS. See https://github.com/klaas/QlaasSwiftPreferencesPane for a working sample project.

Klaas
  • 22,394
  • 11
  • 96
  • 107
  • this doesn't seem to work again. with macOS 10.5.2 and Xcode 11.3.1. it just shows an error when loading the pref pane. (Could not load QlaasSwiftPreferencesPane preference pane.) Setting Always Embed Swift Standard Libraries resolved the error. – EarlGrey Jan 28 '20 at 21:51