@@ -10,84 +10,141 @@ import SwiftUI
1010struct MonospacedFontPicker : View {
1111 var title : String
1212 @Binding var selectedFontName : String
13- @State var recentFonts : [ String ]
14- @State var monospacedFontFamilyNames : [ String ] = [ ]
15- @State var otherFontFamilyNames : [ String ] = [ ]
13+ @State private var recentFonts : [ String ]
14+ @State private var monospacedFontFamilyNames : [ String ] = [ ]
15+ @State private var otherFontFamilyNames : [ String ] = [ ]
1616
1717 init ( title: String , selectedFontName: Binding < String > ) {
1818 self . title = title
1919 self . _selectedFontName = selectedFontName
2020 self . recentFonts = UserDefaults . standard. stringArray ( forKey: " recentFonts " ) ?? [ ]
2121 }
2222
23- private func pushIntoRecentFonts( _ newItem: String ) {
24- recentFonts. removeAll ( where: { $0 == newItem } )
25- recentFonts. insert ( newItem, at: 0 )
26- if recentFonts. count > 3 {
27- recentFonts. removeLast ( )
28- }
29- UserDefaults . standard. set ( recentFonts, forKey: " recentFonts " )
30- }
31-
32- func getFonts( ) {
33- DispatchQueue . global ( qos: . userInitiated) . async {
34- let availableFontFamilies = NSFontManager . shared. availableFontFamilies
35-
36- self . monospacedFontFamilyNames = availableFontFamilies. filter { fontFamilyName in
37- let fontNames = NSFontManager . shared. availableMembers ( ofFontFamily: fontFamilyName) ?? [ ]
38- return fontNames. contains { fontName in
39- guard let font = NSFont ( name: " \( fontName [ 0 ] ) " , size: 14 ) else {
40- return false
41- }
42- return font. isFixedPitch && font. numberOfGlyphs > 26
43- }
44- }
45- . filter { $0 != " SF Mono " }
46-
47- self . otherFontFamilyNames = availableFontFamilies. filter { fontFamilyName in
48- let fontNames = NSFontManager . shared. availableMembers ( ofFontFamily: fontFamilyName) ?? [ ]
49- return fontNames. contains { fontName in
50- guard let font = NSFont ( name: " \( fontName [ 0 ] ) " , size: 14 ) else {
51- return false
52- }
53- return !font. isFixedPitch && font. numberOfGlyphs > 26
54- }
55- }
56- }
57- }
58-
5923 var body : some View {
60- return Picker ( selection: $selectedFontName, label: Text ( title) ) {
24+ Picker ( selection: $selectedFontName, label: Text ( title) ) {
6125 Text ( " System Font " )
6226 . font ( Font ( NSFont . monospacedSystemFont ( ofSize: 13.5 , weight: . medium) ) )
6327 . tag ( " SF Mono " )
28+
6429 if !recentFonts. isEmpty {
6530 Divider ( )
6631 ForEach ( recentFonts, id: \. self) { fontFamilyName in
67- Text ( fontFamilyName) . font ( . custom( fontFamilyName, size: 13.5 ) )
32+ Text ( fontFamilyName)
33+ . font ( . custom( fontFamilyName, size: 13.5 ) )
34+ . tag ( fontFamilyName) // to prevent picker invalid and does not have an associated tag error.
6835 }
6936 }
37+
7038 if !monospacedFontFamilyNames. isEmpty {
7139 Divider ( )
7240 ForEach ( monospacedFontFamilyNames, id: \. self) { fontFamilyName in
73- Text ( fontFamilyName) . font ( . custom( fontFamilyName, size: 13.5 ) )
41+ Text ( fontFamilyName)
42+ . font ( . custom( fontFamilyName, size: 13.5 ) )
43+ . tag ( fontFamilyName)
7444 }
7545 }
46+
7647 if !otherFontFamilyNames. isEmpty {
7748 Divider ( )
78- Picker ( selection : $selectedFontName , label : Text ( " Other fonts... " ) ) {
49+ Menu {
7950 ForEach ( otherFontFamilyNames, id: \. self) { fontFamilyName in
80- Text ( fontFamilyName)
81- . font ( . custom( fontFamilyName, size: 13.5 ) )
51+ Button {
52+ pushIntoRecentFonts ( fontFamilyName)
53+ selectedFontName = fontFamilyName
54+ } label: {
55+ Text ( fontFamilyName)
56+ . font ( . custom( fontFamilyName, size: 13.5 ) )
57+ }
58+ . tag ( fontFamilyName)
8259 }
60+ } label: {
61+ Text ( " Other fonts... " )
8362 }
8463 }
8564 }
8665 . onChange ( of: selectedFontName) { _ in
8766 if selectedFontName != " SF Mono " {
8867 pushIntoRecentFonts ( selectedFontName)
68+
69+ // remove the font to prevent ForEach conflict
70+ monospacedFontFamilyNames. removeAll { $0 == selectedFontName }
71+ otherFontFamilyNames. removeAll { $0 == selectedFontName }
72+ }
73+ }
74+ . task {
75+ await getFonts ( )
76+ }
77+ }
78+ }
79+
80+ extension MonospacedFontPicker {
81+ private func pushIntoRecentFonts( _ newItem: String ) {
82+ recentFonts. removeAll ( where: { $0 == newItem } )
83+ recentFonts. insert ( newItem, at: 0 )
84+ if recentFonts. count > 3 {
85+ recentFonts. removeLast ( )
86+ }
87+ UserDefaults . standard. set ( recentFonts, forKey: " recentFonts " )
88+ }
89+
90+ private func getFonts( ) async {
91+ await withTaskGroup ( of: Void . self) { group in
92+ group. addTask {
93+ let monospacedFontFamilyNames = getMonospacedFamilyNames ( )
94+ await MainActor . run {
95+ self . monospacedFontFamilyNames = monospacedFontFamilyNames
96+ }
97+ }
98+
99+ group. addTask {
100+ let otherFontFamilyNames = getOtherFontFamilyNames ( )
101+ await MainActor . run {
102+ self . otherFontFamilyNames = otherFontFamilyNames
103+ }
89104 }
90105 }
91- . onAppear ( perform: getFonts)
92106 }
107+
108+ private func getMonospacedFamilyNames( ) -> [ String ] {
109+ let availableFontFamilies = NSFontManager . shared. availableFontFamilies
110+
111+ return availableFontFamilies. filter { fontFamilyName in
112+ // exclude the font if it is in recentFonts to prevent ForEach conflict
113+ if recentFonts. contains ( fontFamilyName) {
114+ return false
115+ }
116+
117+ // exclude default font
118+ if fontFamilyName == " SF Mono " {
119+ return false
120+ }
121+
122+ // include the font which is fixedPitch
123+ // include the font which numberOfGlyphs is greater than 26
124+ if let font = NSFont ( name: fontFamilyName, size: 14 ) {
125+ return font. isFixedPitch && font. numberOfGlyphs > 26
126+ } else {
127+ return false
128+ }
129+ }
130+ }
131+
132+ private func getOtherFontFamilyNames( ) -> [ String ] {
133+ let availableFontFamilies = NSFontManager . shared. availableFontFamilies
134+
135+ return availableFontFamilies. filter { fontFamilyName in
136+ // exclude the font if it is in recentFonts to prevent ForEach conflict
137+ if recentFonts. contains ( fontFamilyName) {
138+ return false
139+ }
140+
141+ // include the font which is NOT fixedPitch
142+ // include the font which numberOfGlyphs is greater than 26
143+ if let font = NSFont ( name: fontFamilyName, size: 14 ) {
144+ return !font. isFixedPitch && font. numberOfGlyphs > 26
145+ } else {
146+ return false
147+ }
148+ }
149+ }
93150}
0 commit comments