Skip to content

Commit

Permalink
Merge pull request #18 from martyuiop/feature/fa6-support
Browse files Browse the repository at this point in the history
All changes for FA6 compatibility and FA5 backwards compatibility
  • Loading branch information
mattmaddux authored May 22, 2023
2 parents 95828fd + 77dc211 commit f7e6923
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 f7e6923

Please sign in to comment.