Skip to content

Commit

Permalink
Use preference pane title as window title (#19)
Browse files Browse the repository at this point in the history
Fixes #15
  • Loading branch information
DivineDominion authored and sindresorhus committed Apr 7, 2019
1 parent d9ad07c commit 891d9df
Show file tree
Hide file tree
Showing 10 changed files with 52 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Example/AdvancedPreferenceViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Preferences

final class AdvancedPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = PreferencePaneIdentifier.advanced
let toolbarItemTitle = "Advanced"
let preferencePaneTitle = "Advanced"
let toolbarItemIcon = NSImage(named: NSImage.advancedName)!

override var nibName: NSNib.Name? {
Expand Down
2 changes: 1 addition & 1 deletion Example/GeneralPreferenceViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Preferences

final class GeneralPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = PreferencePaneIdentifier.general
let toolbarItemTitle = "General"
let preferencePaneTitle = "General"
let toolbarItemIcon = NSImage(named: NSImage.preferencesGeneralName)!

override var nibName: NSNib.Name? {
Expand Down
2 changes: 2 additions & 0 deletions Preferences.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
50A412F32196B70100E4A5A8 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencesStyle.swift; sourceTree = "<group>"; usesTabs = 1; };
50A412F52196E87900E4A5A8 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SegmentedControlStyleViewController.swift; sourceTree = "<group>"; usesTabs = 1; };
50A412F72196EAF200E4A5A8 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ToolbarItemStyleViewController.swift; sourceTree = "<group>"; usesTabs = 1; };
50F9AE80225773A0005D7DC3 /* readme.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; usesTabs = 1; };
E3194E4022573FF3006FE775 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
E34E9EE720E6149B002F8F86 /* PreferencesExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PreferencesExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
E34E9EE920E6149B002F8F86 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; usesTabs = 1; };
Expand Down Expand Up @@ -127,6 +128,7 @@
OBJ_5 = {
isa = PBXGroup;
children = (
50F9AE80225773A0005D7DC3 /* readme.md */,
OBJ_6 /* Package.swift */,
OBJ_7 /* Sources */,
E34E9EE820E6149B002F8F86 /* Example */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Preferences/PreferencePane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct PreferencePaneIdentifier: Equatable, RawRepresentable {

public protocol PreferencePane: AnyObject {
var preferencePaneIdentifier: PreferencePaneIdentifier { get }
var toolbarItemTitle: String { get }
var preferencePaneTitle: String { get }
var toolbarItemIcon: NSImage { get }
var viewController: NSViewController { get }
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/Preferences/PreferencesTabViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
defer {
activeTab = index
preferencesStyleController.selectTab(index: index)
updateWindowTitle(tabIndex: index)
}

if activeTab == nil {
Expand All @@ -93,6 +94,18 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
}
}

private func updateWindowTitle(tabIndex: Int) {
self.window.title = {
if preferencePanes.count > 1 {
return preferencePanes[tabIndex].preferencePaneTitle
} else {
let preferences = String(System.localizedString(forKey: "Preferences…").dropLast())
let appName = Bundle.main.appName
return "\(appName) \(preferences)"
}
}()
}

/// Cached constraints that pin childViewController views to the content view
private var activeChildViewConstraints = [NSLayoutConstraint]()

Expand Down
1 change: 0 additions & 1 deletion Sources/Preferences/PreferencesWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public final class PreferencesWindowController: NSWindowController {
)
super.init(window: window)

window.title = String(System.localizedString(forKey: "Preferences…").dropLast())
window.contentViewController = tabViewController
tabViewController.isAnimated = animated
tabViewController.configure(preferencePanes: preferencePanes)
Expand Down
10 changes: 5 additions & 5 deletions Sources/Preferences/SegmentedControlStyleViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
var maxSize = CGSize.zero

for preference in preferences {
let title = preference.toolbarItemTitle
let title = preference.preferencePaneTitle
let titleSize = title.size(
withAttributes: [
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular))
Expand All @@ -87,7 +87,7 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight)

for (index, preference) in preferences.enumerated() {
segmentedControl.setLabel(preference.toolbarItemTitle, forSegment: index)
segmentedControl.setLabel(preference.preferencePaneTitle, forSegment: index)
segmentedControl.setWidth(segmentSize.width, forSegment: index)
if let cell = segmentedControl.cell as? NSSegmentedCell {
cell.setTag(index, forSegment: index)
Expand Down Expand Up @@ -123,11 +123,11 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
let toolbarItemGroup = NSToolbarItemGroup(itemIdentifier: toolbarItemIdentifier)
toolbarItemGroup.view = segmentedControl
toolbarItemGroup.subitems = preferences.enumerated().map { index, preferenceable -> NSToolbarItem in
let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.toolbarItemTitle)"))
item.label = preferenceable.toolbarItemTitle
let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.preferencePaneTitle)"))
item.label = preferenceable.preferencePaneTitle

let menuItem = NSMenuItem(
title: preferenceable.toolbarItemTitle,
title: preferenceable.preferencePaneTitle,
action: #selector(segmentedControlMenuAction),
keyEquivalent: ""
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Preferences/ToolbarItemStyleViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController
}

let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier)
toolbarItem.label = preference.toolbarItemTitle
toolbarItem.label = preference.preferencePaneTitle
toolbarItem.image = preference.toolbarItemIcon
toolbarItem.target = self
toolbarItem.action = #selector(toolbarItemSelected)
Expand Down
14 changes: 14 additions & 0 deletions Sources/Preferences/util.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ extension NSEvent {
return NSEvent.userInteractionEvents.contains(type)
}
}

extension Bundle {
var appName: String {
return string(forInfoDictionaryKey: "CFBundleDisplayName")
?? string(forInfoDictionaryKey: "CFBundleName")
?? string(forInfoDictionaryKey: "CFBundleExecutable")
?? "<Unknown App Name>"
}

private func string(forInfoDictionaryKey key: String) -> String? {
// `object(forInfoDictionaryKey:)` prefers localized info dictionary over the regular one automatically
return object(forInfoDictionaryKey: key) as? String
}
}
17 changes: 14 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import Preferences

final class GeneralPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = PreferencePaneIdentifier.general
let toolbarItemTitle = "General"
let preferencePaneTitle = "General"
let toolbarItemIcon = NSImage(named: NSImage.preferencesGeneralName)!

override var nibName: NSNib.Name? {
Expand All @@ -87,7 +87,7 @@ import Preferences

final class AdvancedPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = PreferencePaneIdentifier.advanced
let toolbarItemTitle = "Advanced"
let preferencePaneTitle = "Advanced"
let toolbarItemIcon = NSImage(named: NSImage.advancedName)!

override var nibName: NSNib.Name? {
Expand Down Expand Up @@ -160,7 +160,7 @@ lazy var preferencesWindowController = PreferencesWindowController(
```swift
public protocol PreferencePane: AnyObject {
var preferencePaneIdentifier: PreferencePaneIdentifier { get }
var toolbarItemTitle: String { get }
var preferencePaneTitle: String { get }
var toolbarItemIcon: NSImage { get } // Not required when using the .`segmentedControl` style
}

Expand All @@ -185,6 +185,17 @@ As usual, call `NSWindowController#close()` to close the preferences window.

## FAQ

### How can I localize the window title?

The `PreferencesWindowController` adheres to the [Apple HIG](https://developer.apple.com/design/human-interface-guidelines/macos/app-architecture/preferences/) and uses this set of rules to determine the window title:

- **Multiple preference panes:** Uses the currently selected `preferencePaneTitle` as the window title. Localize your `preferencePaneTitle`s to get localized window titles.
- **Single preference pane:** Sets the window title to `APPNAME Preferences`. The app name is obtained from your app's bundle. You can localize its `Info.plist` to customize the title. The `Preferences` part is taken from the "Preferences…" menu item, see #12. The order of lookup for the app name from your bundle:
1. `CFBundleDisplayName`
2. `CFBundleName`
3. `CFBundleExecutable`
4. Fall back to `"<Unknown App Name>"` to show you're missing some settings.

### How is it better than [`MASPreferences`](https://github.com/shpakovski/MASPreferences)?

- Written in Swift. *(No bridging header!)*
Expand Down

0 comments on commit 891d9df

Please sign in to comment.