概要
- 現状(2023-10月)、NSColorPanelのSwiftUIの代替にColorPickerが出ているが、NSFontPanelのSwiftUIの代替はでていない。
- そのため
NSFontPanel
をSwiftUIで扱えるピッカー用のViewを作成してみる。 - (下記の
Select...
ボタンが今回作成したフォントピッカー)

参考
GitHub
実装
- Bindingで
NSFont
を渡し、裏でNSFontPanel
を呼び出して変更を反映させる。 - これによりSwiftUI側では特に意識せずに
NSFont
を渡してNSFontPanel
のUIでフォントの更新が可能となる。
import SwiftUI
/// refs: [tyagishi / FontPicker](https://github.com/tyagishi/FontPicker/tree/main)
fileprivate class FontPickerDelegate {
private var parent: FontPicker
init(_ parent: FontPicker) {
self.parent = parent
}
@objc
private func changeFont(_ id: Any) {
parent.fontSelected()
}
}
struct FontPicker: View {
private let label: LocalizedStringKey?
@Binding private var font: NSFont
@State private var fontPickerDelegate: FontPickerDelegate? = nil
init(_ label: LocalizedStringKey? = nil, selection: Binding<NSFont>) {
self.label = label
self._font = selection
}
var body: some View {
Button {
fontPickerDelegate = FontPickerDelegate(self)
NSFontManager.shared.target = fontPickerDelegate
NSFontPanel.shared.setPanelFont(font, isMultiple: false)
NSFontPanel.shared.orderBack(nil)
} label: {
if let label {
Text(label)
} else {
EmptyView()
}
}
}
fileprivate func fontSelected() {
font = NSFontPanel.shared.convert(self.font)
}
}
- また今回フォント名とサイズを保存したいので、
FontData
というカスタムクラスを作成し、これを@AppStorage
で保存している
struct FontData {
var name: String
var pointSize: CGFloat
}
- 外部的には
NSFont
を公開して、内部的にFontData
でデータの保存を行っている。
public class Settings: ObservableObject {
static let shared = Settings()
@AppStorage("selected-font")
private var fontData: FontData = .init(name: "Helvetica", pointSize: 14)
var nsFont: NSFont {
get {
fontData.nsFont
}
set(newValue) {
fontData = newValue.fontData
}
}
}
- 呼び出し例は以下の通り。
import SwiftUI
struct ContentView: View {
@ObservedObject var settings = Settings.shared
var body: some View {
Form {
Section {
HStack {
...
FontPicker("Select...", selection: $settings.nsFont)
}
...
.frame(maxWidth: .infinity, alignment: .trailing)
}
Section {
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...")
.font(Font(settings.nsFont))
} header: {
Text("Preview")
}
}
.formStyle(.grouped)
}
}