Reputation: 15058
Summary
macOS 14 Sonoma adds the "Text Size" setting under Settings -> Accessibility -> Display. This is similar to the "Text Size" setting that has been in iOS for quite some time.
Many Apple apps automatically adjust the size of their text based on this setting. This includes Xcode, Settings, Finder, Mail, Notes, and others. Apps like Xcode and Settings only change the text in sidebars while the other apps change text throughout the app.
Question
My question is how to respond to this setting in my own macOS app?
Research
I've searched the AppKit documentation. I've searched WWDC videos. I've searched the Apple developer forums. There's no mention of how this is done that I have seen.
Here on SO I've seen:
NSTextField
.Experiments
I've created a native macOS app using AppKit. I've added an NSTextField
and an NSTextView
and set the fonts to a text style such as "Title 1". Changing the setting does not automatically change the displayed font size in the app.
I've created a SwiftUI app and added a native macOS build. I setup a ContentView
with some Text
using Text("This is some text").font(.title)
. Changing the setting does not automatically change the displayed font size in the app.
I have an iOS app built for macOS using Mac Catalyst. The iOS version of the app automatically adjusts its text when the "Text Size" setting is changed in the iOS Settings app. The macOS version of the app does not change at all when the "Text Size" setting is changed in the macOS Settings app.
In the native AppKit app I tried the following code in the AppDelegate
:
NSWorkspace.shared.notificationCenter.addObserver(forName: nil, object: nil, queue: nil) { note in
print(note)
}
NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { note in
print(note)
}
Neither of these generated any output when the "Text Size" setting was changed. Under iOS/UIKit, you get a UIContentSizeCategory.didChangeNotification
notification.
As a shot in the dark I setup the AppKit app with my own custom NSApplication
subclass and added the following:
class Application: NSApplication {
override func sendEvent(_ event: NSEvent) {
print("event \(event)")
super.sendEvent(event)
}
override func sendAction(_ action: Selector, to target: Any?, from sender: Any?) -> Bool {
print("action: \(action)")
return super.sendAction(action, to: target, from: sender)
}
}
Neither of these produced output when the "Text Size" setting was changed.
Summary
I'm at a loss at this point. What code is needed to allow a macOS app to respond to changes to the "Text Size" setting? My immediate need is with an iOS/UIKit app built for macOS with Mac Catalyst but I'm fine with solutions in native AppKit or even SwiftUI. Code can be in either Swift or Objective-C.
Upvotes: 3
Views: 1280
Reputation: 586
defaults
Text size are stored in
com.apple.universalaccess.plist
as FontSizeCategory
:
$ defaults read com.apple.universalaccess FontSizeCategory
# {
# "com.apple.MobileSMS" = UseGlobal;
# "com.apple.Notes" = UseGlobal;
# "com.apple.finder" = UseGlobal;
# "com.apple.iBooksX" = UseGlobal;
# "com.apple.iCal" = UseGlobal;
# "com.apple.mail" = UseGlobal;
# "com.apple.news" = UseGlobal;
# "com.apple.stocks" = UseGlobal;
# "com.apple.weather" = UseGlobal;
# global = DEFAULT;
# version = "3.0";
# }
global
is what we're interested in.
Here are a list of possible values and corresponding options in the Settings app:
Value | Text Size | Sidebar Icon Size |
---|---|---|
XXXS |
9pt | Small |
XXS |
10pt | Small |
XS |
11pt | Small |
S |
12pt | Medium |
DEFAULT |
Default (11pt) | Medium |
M |
13pt | Medium |
L |
14pt | Medium |
XL |
15pt | Large |
XXL |
16pt | Large |
XXXL |
17pt | Large |
AX1 |
20pt | Large |
AX2 |
24pt | Large |
AX3 |
29pt | Large |
AX4 |
35pt | Large |
AX5 |
42pt | Large |
To observe text size changes:
Create a UserDefaults
object with suite name com.apple.universalaccess
. Make sure you keep a strong reference to it, or you're not getting KVO callbacks.
Observe changes for key FontSizeCategory
with KVO.
extension UserDefaults {
// I'm using `observe(_:options:changeHandler:)` below and it only accept
// a `KeyPath` key path, so this is required for this use case.
// Notice that the property name is the same as the key `FontSizeCategory`,
// because the property name is used for KVO key.
@objc var FontSizeCategory: [String: Any]? {
dictionary(forKey: "FontSizeCategory")
}
}
let universalAccessDefaults = UserDefaults(suiteName: "com.apple.universalaccess")!
var fontSizeCategoryObservation: NSKeyValueObservation?
fontSizeCategoryObservation = universalAccessDefaults.observe(\.FontSizeCategory, options: .new) { _, changes in
guard
let newValue = changes.newValue.flatMap(\.self),
let global = newValue["global"] as? String
else { return }
/* Update your UI for text size change */
}
Some said that this requires a temporary exception entitlement and is usually not allowed for App Store.
Unfortunately, we don't have dynamic type in macOS APIs yet. Methods like NSFont.preferredFont(forTextStyle:)
always return a fixed font size.
For now, we would need to maintain a list of font size our own, migrated from iOS. This is a lot of works, and I barely know anything about typography, so I'm leaving this to you.
Here are some links that might be useful:
Upvotes: 2