From 77dc21153f3ab19bf55e00e67f405134b4a0356b Mon Sep 17 00:00:00 2001 From: Martin Dorschler Date: Thu, 18 May 2023 11:46:12 +0100 Subject: [PATCH] All changes for FA6 compatibility and FA5 backwards compatibility README updated to advise on changes New thin Style added, FA6 files added to FACollection. FA6 changes over 300 FA5 font names and moves the old names to an aliases array in the json. Added this array to the decoder, added a method which try to return an icon by its alias if it is not found by its name. One of the icon name changes was the question-circle which became circle-question. To avoid a logic loop i hardcoded a check to try for this name should the new one not be found. --- README.md | 18 +++-- Sources/FASwiftUI/FAIcon.swift | 118 +++++++++++++++++++++------- Sources/FASwiftUI/FAText.swift | 14 +++- Sources/FASwiftUI/FontAwesome.swift | 13 +++ 4 files changed, 128 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 30749a9..a40c308 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FASwiftUI -Easily integrate FontAwesome into your SwiftUI projects. Use Font Awesome icons as text in your SwiftUI views. Supports Font Awesome Pro or Free. +Easily integrate FontAwesome 5 or 6 into your SwiftUI projects. Use Font Awesome icons as text in your SwiftUI views. Supports Font Awesome Pro or Free. (Does not currently support the Duotone style.) @@ -14,10 +14,16 @@ Easily integrate FontAwesome into your SwiftUI projects. Use Font Awesome icons 2. Download the Pro or Free version 3. Drag the following files from the download to your project: * icons.json - * Font Awesome 5 Brands-Regular-400.otf - * Font Awesome 5 [Free/Pro]-Regular-400.otf - * Font Awesome 5 [Free/Pro]-Solid-900.otf - * Font Awesome 5 Pro-Light-300.otf (Pro Only) + AND EITHER + * Font Awesome 6 Brands-Regular-400.otf + * Font Awesome 6 [Free/Pro]-Regular-400.otf + * Font Awesome 6 [Free/Pro]-Solid-900.otf + * Font Awesome 6 Pro-Light-300.otf (Pro Only) + OR + * Font Awesome 5 Brands-Regular-400.otf + * Font Awesome 5 [Free/Pro]-Regular-400.otf + * Font Awesome 5 [Free/Pro]-Solid-900.otf + * Font Awesome 5 Pro-Light-300.otf (Pro Only) 4. Add files to target - For each of the files in the last step: 1. Select the file in Project Navigator 2. Open the Inspectors bar on the right and select the file inspector (first tab) @@ -77,4 +83,4 @@ struct ContentView: View { } ``` -![Regualr Icon Screenshot](https://raw.githubusercontent.com/mattmaddux/FASwiftUI/master/icon-red.png) \ No newline at end of file +![Regualr Icon Screenshot](https://raw.githubusercontent.com/mattmaddux/FASwiftUI/master/icon-red.png) diff --git a/Sources/FASwiftUI/FAIcon.swift b/Sources/FASwiftUI/FAIcon.swift index 2c3978b..557aa70 100644 --- a/Sources/FASwiftUI/FAIcon.swift +++ b/Sources/FASwiftUI/FAIcon.swift @@ -18,13 +18,18 @@ public enum FAStyle: String, Codable { case solid case brands case duotone - + // FA6 added thin style + case thin + var weight: Font.Weight { switch self { case .light: return .light case .solid: return .heavy + // FA6 added thin style + case .thin: + return .thin default: return .regular } @@ -36,24 +41,28 @@ public enum FAStyle: String, Codable { // ======================================================= // enum FACollection: String { - case pro = "Font Awesome 5 Pro" - case free = "Font Awesome 5 Free" - case brands = "Font Awesome 5 Brands" - + case pro = "Font Awesome 6 Pro" + case free = "Font Awesome 6 Free" + case brands = "Font Awesome 6 Brands" + // FA5 backwards compatibility + case pro5 = "Font Awesome 5 Pro" + case free5 = "Font Awesome 5 Free" + case brands5 = "Font Awesome 5 Brands" + static var availableCollection: [FACollection] { var result = [FACollection]() - if FACollection.isAvailable(collection: .pro) { + if FACollection.isAvailable(collection: .pro) || FACollection.isAvailable(collection: .pro5) { result.append(.pro) } - if FACollection.isAvailable(collection: .free) { + if FACollection.isAvailable(collection: .free) || FACollection.isAvailable(collection: .free5) { result.append(.free) } - if FACollection.isAvailable(collection: .brands) { + if FACollection.isAvailable(collection: .brands) || FACollection.isAvailable(collection: .brands5) { result.append(.brands) } return result } - + static func isAvailable(collection: FACollection) -> Bool { #if os(iOS) return UIFont.familyNames.contains(collection.rawValue) @@ -68,18 +77,19 @@ enum FACollection: String { // ======================================================= // public struct FAIcon: Identifiable, Decodable, Comparable { - + // ======================================================= // // MARK: - Properties // ======================================================= // - + public var id: String? public var label: String public var unicode: String public var styles: [FAStyle] public var searchTerms: [String] - - + // FA6 changed many names. Old names stored in aliases array + public var aliasNames: [String]? = [] + var collection: FACollection { if styles.contains(.brands) { return .brands @@ -89,54 +99,78 @@ public struct FAIcon: Identifiable, Decodable, Comparable { return .free } } - + + // many FA6 unicodes do not provide 4 characters so adding leading zeros + var paddedCode: String { + var code = self.unicode + while code.count < 4 { + code = "0" + code + } + return code + } + var unicodeString: String { - let rawMutable = NSMutableString(string: "\\u\(self.unicode)") + let rawMutable = NSMutableString(string: "\\u\(paddedCode)") CFStringTransform(rawMutable, nil, "Any-Hex/Java" as NSString, true) return rawMutable as String } - + // ======================================================= // // MARK: - Initializer // ======================================================= // - + public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) label = try values.decode(String.self, forKey: .label) unicode = try values.decode(String.self, forKey: .unicode) styles = try values.decode([FAStyle].self, forKey: .styles) - + let search = try values.nestedContainer(keyedBy: SearchKeys.self, forKey: .search) let rawSearchTerms = try search.decode([RawSearchTerm].self, forKey: .terms) searchTerms = [String]() for term in rawSearchTerms { searchTerms.append(term.toString()) } + // FA6 changed many names. Old names stored in aliases array + let aliases = try? values.nestedContainer(keyedBy: AliasKeys.self, forKey: .aliases) + let rawAliases = try? aliases?.decode([RawAlias].self, forKey: .names) + aliasNames = [String]() + for name in rawAliases ?? [] { + if aliasNames?.append(name.toString()) == nil { + aliasNames = [name.toString()] + } + } } - + // ======================================================= // // MARK: - Coding Keys // ======================================================= // - + public enum CodingKeys: String, CodingKey { case label case unicode case styles case search + // FA6 changed many names. Old names stored in aliases array + case aliases } - + public enum SearchKeys: String, CodingKey { case terms } - + // FA6 changed many icon names and moved the old ones to an aliases array. + public enum AliasKeys: String, CodingKey { + case names + } + // ======================================================= // // MARK: - Decoding Helper Types // ======================================================= // - + enum RawSearchTerm: Decodable { case int(Int) case string(String) - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { @@ -149,7 +183,35 @@ public struct FAIcon: Identifiable, Decodable, Comparable { } } } - + + func toString() -> String { + switch self { + case .int(let storedInt): + return String(storedInt) + case .string(let storedString): + return storedString + } + } + } + + // FA6 changed many icon names and moved the old ones to an aliases array. Decoder modified accordingly + enum RawAlias: Decodable { + case int(Int) + case string(String) + + init(from decoder: Decoder) throws { + let container = try? decoder.singleValueContainer() + do { + self = try .int(container?.decode(Int.self) ?? 0) + } catch DecodingError.typeMismatch { + do { + self = try .string(container?.decode(String.self) ?? "") + } catch DecodingError.typeMismatch { + throw DecodingError.typeMismatch(RawAlias.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)")) + } + } + } + func toString() -> String { switch self { case .int(let storedInt): @@ -159,15 +221,15 @@ public struct FAIcon: Identifiable, Decodable, Comparable { } } } - + // ======================================================= // // MARK: - Comparable // ======================================================= // - + public static func < (lhs: FAIcon, rhs: FAIcon) -> Bool { return lhs.id ?? lhs.label < lhs.id ?? rhs.label } - + public static func == (lhs: FAIcon, rhs: FAIcon) -> Bool { return lhs.id ?? lhs.label == lhs.id ?? rhs.label } diff --git a/Sources/FASwiftUI/FAText.swift b/Sources/FASwiftUI/FAText.swift index 4afc4e4..3347068 100644 --- a/Sources/FASwiftUI/FAText.swift +++ b/Sources/FASwiftUI/FAText.swift @@ -26,7 +26,19 @@ public struct FAText: View { if let icon = FontAwesome.shared.icon(byName: self.iconName) { self.icon = icon } else { - self.icon = FontAwesome.shared.icon(byName: "question-circle")! + // Many FA5 names change in FA6 and old names added to aliases array + if let iconAlias = FontAwesome.shared.icon(byAlias: self.iconName) { + self.icon = iconAlias + } else { + // Backwards compatibility for FA5 vs FA6 as font ID for circle-question has changed + // if icon not found use FA5 question-circle + if let iconFailed5 = FontAwesome.shared.icon(byName: "question-circle") { + self.icon = iconFailed5 + } else { + // if question-circle not found use FA6 circle-question + self.icon = FontAwesome.shared.icon(byName: "circle-question")! + } + } self.style = .regular print("FASwiftUI: Icon \"\(iconName)\" not found. Check list at https://fontawesome.com/icons for set availability.") } diff --git a/Sources/FASwiftUI/FontAwesome.swift b/Sources/FASwiftUI/FontAwesome.swift index 3ae5ec4..f4cc33c 100644 --- a/Sources/FASwiftUI/FontAwesome.swift +++ b/Sources/FASwiftUI/FontAwesome.swift @@ -46,6 +46,19 @@ public class FontAwesome { return store[name.lowercased()] } + // icon(byAlias:) added to allow FA5 backwards compatibility where names have changed and moved to an aliases array + public func icon(byAlias name: String) -> FAIcon? { + var iconName = "" + for item in store { + if let aliasNames = item.value.aliasNames, aliasNames.contains(name) { + iconName = item.value.id ?? name + print("found \(name) by alias as \(iconName)") + return store[iconName.lowercased()] + } + } + return store[iconName.lowercased()] + } + public func search(query: String) -> [String: FAIcon] { let filtered = store.filter() { if $0.key.contains(query) {