Skip to content

Commit

Permalink
All changes for FA6 compatibility and FA5 backwards compatibility
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
martyuiop committed May 18, 2023
1 parent 95828fd commit 77dc211
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 35 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.)

Expand All @@ -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)
Expand Down Expand Up @@ -77,4 +83,4 @@ struct ContentView: View {
}
```

![Regualr Icon Screenshot](https://raw.githubusercontent.com/mattmaddux/FASwiftUI/master/icon-red.png)
![Regualr Icon Screenshot](https://raw.githubusercontent.com/mattmaddux/FASwiftUI/master/icon-red.png)
118 changes: 90 additions & 28 deletions Sources/FASwiftUI/FAIcon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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):
Expand All @@ -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
}
Expand Down
14 changes: 13 additions & 1 deletion Sources/FASwiftUI/FAText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/FASwiftUI/FontAwesome.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 77dc211

Please sign in to comment.