diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..95c4320
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..9da10cf
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,16 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "HalfASheet",
+ "repositoryURL": "https://github.com/franklynw/HalfASheet.git",
+ "state": {
+ "branch": null,
+ "revision": "2f1959c5e8a74cb078b96942d46388b7e4bebcdb",
+ "version": "1.0.0"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..3a2ca2c
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,27 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "FontPicker",
+ platforms: [
+ .iOS(.v14)
+ ],
+ products: [
+ .library(
+ name: "FontPicker",
+ targets: ["FontPicker"]),
+ ],
+ dependencies: [
+ .package(name: "HalfASheet", url: "https://github.com/franklynw/HalfASheet.git", .upToNextMajor(from: "1.0.0"))
+ ],
+ targets: [
+ .target(
+ name: "FontPicker",
+ dependencies: ["HalfASheet"]),
+ .testTarget(
+ name: "FontPickerTests",
+ dependencies: ["FontPicker"]),
+ ]
+)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6ecd1c6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# FontPicker
+
+A description of this package.
diff --git a/Sources/FontPicker/FontPicker+Modifiers.swift b/Sources/FontPicker/FontPicker+Modifiers.swift
new file mode 100644
index 0000000..433af65
--- /dev/null
+++ b/Sources/FontPicker/FontPicker+Modifiers.swift
@@ -0,0 +1,78 @@
+//
+// File.swift
+//
+//
+// Created by Franklyn Weber on 04/02/2021.
+//
+
+import SwiftUI
+
+
+extension FontPicker {
+
+ /// The color to use for the background of the picker
+ /// - Parameter backgroundColor: a UIColor
+ public func backgroundColor(_ backgroundColor: UIColor) -> Self {
+ var copy = self
+ copy.backgroundColor = backgroundColor
+ return copy
+ }
+
+ /// Normally, selecting a font will dismiss the picker. Use this to disable that functionality (eg if you have a preview which shows how the font will look in context)
+ public var disableDismissOnSelection: Self {
+ var copy = self
+ copy._dismissOnSelection = false
+ return copy
+ }
+
+ /// Use this to display only the fonts you want
+ /// - Parameter fontNames: the names of the fonts you want to display
+ public func fontNames(_ fontNames: [String]) -> Self {
+ var copy = self
+ copy.userFontNames = fontNames
+ return copy
+ }
+
+ /// Add your own fonts to the default list of those provided by the system
+ /// - Parameter fontNames: the names of the fonts you want to add
+ public func additionalFontNames(_ fontNames: [String]) -> Self {
+ var copy = self
+ copy.additionalFontNames = fontNames
+ return copy
+ }
+
+ /// If there are any fonts you don't want to appear, specify them here. By default, "Bodoni Ornaments", "Damascus" & "Hiragino" are excluded as they render in an odd way, with bits chopped off.
+ /// To reinstate them, specify an empty array here
+ /// - Parameter fontNames: the names of the fonts you want to add
+ public func excludedFontNames(_ fontNames: [String]) -> Self {
+ var copy = self
+ copy.excludedFontNames = fontNames
+ return copy
+ }
+}
+
+
+extension View {
+
+ /// View extension in the style of .sheet - lacks a couple of customisation options. If more flexibility is required, use FontPicker(...) directly, and apply the required modifiers
+ /// - Parameters:
+ /// - isPresented: binding to a Bool which controls whether or not to show the picker
+ /// - selected: binding to a String for the selected font name
+ func fontPicker(isPresented: Binding, selected: Binding) -> some View {
+ modifier(FontPickerPresentationModifier(content: { FontPicker(isPresented: isPresented, selected: selected)}))
+ }
+}
+
+
+struct FontPickerPresentationModifier: ViewModifier {
+
+ var content: () -> FontPicker
+
+ init(@ViewBuilder content: @escaping () -> FontPicker) {
+ self.content = content
+ }
+
+ func body(content: Content) -> some View {
+ self.content()
+ }
+}
diff --git a/Sources/FontPicker/FontPicker.swift b/Sources/FontPicker/FontPicker.swift
new file mode 100644
index 0000000..ed69033
--- /dev/null
+++ b/Sources/FontPicker/FontPicker.swift
@@ -0,0 +1,93 @@
+//
+// FontPicker.swift
+// HandyList
+//
+// Created by Franklyn Weber on 28/01/2021.
+//
+
+import SwiftUI
+import HalfASheet
+
+
+struct FontPicker: View {
+
+ @Binding private var isPresented: Bool
+ @Binding private var selection: String
+
+ internal var userFontNames: [String]?
+ internal var additionalFontNames: [String] = []
+
+ // these fonts render in an odd way, with tops or bottoms being chopped off, or being nonsense for our purposes (such as symbols)
+ // However, if the user wants them, they just need to specify [] as the excludedFontNames
+ internal var excludedFontNames = [
+ "Bodoni Ornaments",
+ "Damascus",
+ "Hiragino"
+ ]
+
+ internal var backgroundColor: UIColor = .systemBackground
+ internal var _dismissOnSelection = true
+
+
+ /// Initialiser
+ /// - Parameters:
+ /// - isPresented: binding to a Bool which controls whether or not to show the picker
+ /// - selected: binding to a String for the selected font name
+ init(isPresented: Binding, selected: Binding) {
+ _isPresented = isPresented
+ _selection = selected
+ }
+
+ var body: some View {
+
+ HalfASheet(isPresented: $isPresented, title: "Font") {
+
+ ZStack {
+
+ RoundedRectangle(cornerRadius: 10)
+ .foregroundColor(Color(UIColor.lightGray.withAlphaComponent(0.2)))
+ RoundedRectangle(cornerRadius: 10)
+ .foregroundColor(Color(backgroundColor))
+
+ List {
+ ForEach(fontNamesToDisplay(), id: \.self) { fontName in
+
+ HStack {
+ Text(fontName)
+ .customFont(name: fontName)
+ Spacer()
+ }
+ .contentShape(Rectangle())
+ .onTapGesture {
+ selection = fontName
+ if _dismissOnSelection {
+ isPresented = false
+ }
+ }
+ }
+ .listRowBackground(Color.clear)
+ }
+ }
+ .onAppear {
+ NotificationCenter.default.post(name: .fontPickerAppeared, object: self)
+ }
+ .onDisappear {
+ NotificationCenter.default.post(name: .fontPickerDisappeared, object: self)
+ }
+ }
+ .backgroundColor(backgroundColor)
+ .closeButtonColor(UIColor.gray.withAlphaComponent(0.4))
+ .disableDragToDismiss
+ }
+
+ private func fontNamesToDisplay() -> [String] {
+
+ if let userFontNames = userFontNames {
+ return userFontNames
+ }
+
+ let fontNames = UIFont.familyNames.sorted().filter { !excludedFontNames.contains(where: $0.contains) }
+
+ return (fontNames + additionalFontNames).sorted()
+ }
+}
diff --git a/Sources/FontPicker/Helpers/CustomFont.swift b/Sources/FontPicker/Helpers/CustomFont.swift
new file mode 100644
index 0000000..8641bde
--- /dev/null
+++ b/Sources/FontPicker/Helpers/CustomFont.swift
@@ -0,0 +1,99 @@
+//
+// CustomFont.swift
+//
+// Created by Franklyn on 25/06/2019.
+// Copyright © 2019 Franklyn. All rights reserved.
+//
+
+import SwiftUI
+
+
+struct CustomFont: ViewModifier {
+
+ let name: String
+ let size: CGFloat?
+ let style: Font.TextStyle?
+ let weight: Font.Weight
+ let maxSize: CGFloat?
+
+ init(name: String, size: CGFloat? = nil, weight: Font.Weight = .regular, relativeTo style: Font.TextStyle? = nil) {
+ self.name = name
+ self.style = style
+ self.size = size
+ self.weight = weight
+ maxSize = nil
+ }
+
+ init(name: String, weight: Font.Weight = .regular, relativeTo style: Font.TextStyle, maxSize: CGFloat) {
+ self.name = name
+ self.weight = weight
+ self.maxSize = maxSize
+ self.style = style
+ size = nil
+ }
+
+
+ /// Will trigger the refresh of the view when the ContentSizeCategory changes.
+ @Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
+
+ func body(content: Content) -> some View {
+
+ if let maxSize = maxSize, let style = style {
+ let fontSize = min(Self.textSize(for: style), maxSize)
+ return content.font(Font.custom(self.name, size: Self.scaledSize(for: fontSize, style: style)).weight(weight))
+ }
+
+ if let size = self.size {
+ if let style = style {
+ return content.font(Font.custom(name, size: size, relativeTo: style).weight(weight))
+ } else {
+ return content.font(Font.custom(name, size: size).weight(weight))
+ }
+ }
+
+ guard let style = self.style else {
+ return content.font(Font.custom(name, size: Self.textSize(for: .body)).weight(weight))
+ }
+ guard let size = self.fontSizes[style] else {
+ return content.font(Font.custom(name, size: Self.textSize(for: .body)).weight(weight))
+ }
+
+ return content.font(Font.custom(self.name, size: size, relativeTo: .body).weight(weight))
+ }
+
+
+ private let fontSizes: [Font.TextStyle: CGFloat] = [
+ .largeTitle: 34,
+ .title: 28,
+ .title2: 22,
+ .title3: 20,
+ .headline: 17,
+ .body: 17,
+ .callout: 16,
+ .subheadline: 15,
+ .footnote: 13,
+ .caption: 12,
+ .caption2: 11
+ ]
+
+ private static func scaledSize(for size: CGFloat, style: Font.TextStyle) -> CGFloat {
+ let scale = UIFontMetrics.default.scaledValue(for: size)
+ return size * size / scale
+ }
+
+ private static func textSize(for textStyle: Font.TextStyle) -> CGFloat {
+ return UIFont.preferredFont(style: textStyle.uiStyle).pointSize
+ }
+}
+
+
+extension Text {
+
+ func customFont(name: String, size: CGFloat? = nil, weight: Font.Weight = .regular, relativeTo style: Font.TextStyle? = nil) -> ModifiedContent {
+ return modifier(CustomFont(name: name, size: size, weight: weight, relativeTo: style))
+ }
+
+ func customFont(name: String, weight: Font.Weight = .regular, relativeTo style: Font.TextStyle, maxSize: CGFloat) -> ModifiedContent {
+ return modifier(CustomFont(name: name, weight: weight, relativeTo: style, maxSize: maxSize))
+ }
+}
diff --git a/Sources/FontPicker/Helpers/Font.TextStyle+Extensions.swift b/Sources/FontPicker/Helpers/Font.TextStyle+Extensions.swift
new file mode 100644
index 0000000..2f4f973
--- /dev/null
+++ b/Sources/FontPicker/Helpers/Font.TextStyle+Extensions.swift
@@ -0,0 +1,47 @@
+//
+// Font.TextStyle+Extensions.swift
+//
+// Created by Franklyn Weber on 02/02/2021.
+//
+
+import SwiftUI
+
+
+extension Font.TextStyle {
+
+ var uiStyle: UIFont.TextStyle {
+ switch self {
+ case .largeTitle: return .largeTitle
+ case .title: return .title1
+ case .title2: return .title2
+ case .title3: return .title3
+ case .headline: return .headline
+ case .body: return .body
+ case .callout: return .callout
+ case .subheadline: return .subheadline
+ case .footnote: return .footnote
+ case .caption: return .caption1
+ case .caption2: return .caption2
+ @unknown default:
+ return .body
+ }
+ }
+}
+
+
+extension Font.Weight {
+
+ var uiWeight: UIFont.Weight {
+ switch self {
+ case .thin: return .thin
+ case .light: return .light
+ case .regular: return .regular
+ case .medium: return .medium
+ case .semibold: return .semibold
+ case .bold: return .bold
+ case .heavy: return .heavy
+ case .black: return .black
+ default: return .regular
+ }
+ }
+}
diff --git a/Sources/FontPicker/Helpers/Notification.Name+Extensions.swift b/Sources/FontPicker/Helpers/Notification.Name+Extensions.swift
new file mode 100644
index 0000000..ee06ed6
--- /dev/null
+++ b/Sources/FontPicker/Helpers/Notification.Name+Extensions.swift
@@ -0,0 +1,15 @@
+//
+// File.swift
+//
+//
+// Created by Franklyn Weber on 04/02/2021.
+//
+
+import Foundation
+
+
+extension Notification.Name {
+
+ public static let fontPickerAppeared = Notification.Name("FontPickerAppearedNotification")
+ public static let fontPickerDisappeared = Notification.Name("FontPickerDisappearedNotification")
+}
diff --git a/Sources/FontPicker/Helpers/UIFont+PreferredFont.swift b/Sources/FontPicker/Helpers/UIFont+PreferredFont.swift
new file mode 100644
index 0000000..29ca2cb
--- /dev/null
+++ b/Sources/FontPicker/Helpers/UIFont+PreferredFont.swift
@@ -0,0 +1,33 @@
+//
+// UIFont+PreferredFont.swift
+//
+// Created by Franklyn Weber on 08/07/2020.
+//
+
+import UIKit
+
+
+extension UIFont {
+
+ class func preferredFont(forFamily family: String? = nil,
+ style: UIFont.TextStyle,
+ weight: UIFont.Weight = .regular) -> UIFont {
+
+ let fontDescriptor = self.fontDescriptor(for: style)
+
+ let attributes: [UIFontDescriptor.AttributeName : Any] = [
+ .family: family ?? UIFont.systemFont(ofSize: 12).familyName,
+ .traits: [UIFontDescriptor.TraitKey.weight: weight],
+ .size: fontDescriptor.pointSize
+ ]
+
+ let customFontDescriptor = UIFontDescriptor(fontAttributes: attributes)
+
+ return UIFont(descriptor: customFontDescriptor, size: 0)
+ }
+
+ private static func fontDescriptor(for textStyle: UIFont.TextStyle) -> UIFontDescriptor {
+ let traitCollection = UITraitCollection(preferredContentSizeCategory: UIContentSizeCategory.large)
+ return .preferredFontDescriptor(withTextStyle: textStyle, compatibleWith: traitCollection)
+ }
+}
diff --git a/Tests/FontPickerTests/FontPickerTests.swift b/Tests/FontPickerTests/FontPickerTests.swift
new file mode 100644
index 0000000..81c42a3
--- /dev/null
+++ b/Tests/FontPickerTests/FontPickerTests.swift
@@ -0,0 +1,14 @@
+import XCTest
+@testable import FontPicker
+
+final class FontPickerTests: XCTestCase {
+ func testExample() {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct
+ // results.
+ }
+
+ static var allTests = [
+ ("testExample", testExample),
+ ]
+}
diff --git a/Tests/FontPickerTests/XCTestManifests.swift b/Tests/FontPickerTests/XCTestManifests.swift
new file mode 100644
index 0000000..ae4225e
--- /dev/null
+++ b/Tests/FontPickerTests/XCTestManifests.swift
@@ -0,0 +1,9 @@
+import XCTest
+
+#if !canImport(ObjectiveC)
+public func allTests() -> [XCTestCaseEntry] {
+ return [
+ testCase(FontPickerTests.allTests),
+ ]
+}
+#endif
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
new file mode 100644
index 0000000..12a66c5
--- /dev/null
+++ b/Tests/LinuxMain.swift
@@ -0,0 +1,7 @@
+import XCTest
+
+import FontPickerTests
+
+var tests = [XCTestCaseEntry]()
+tests += FontPickerTests.allTests()
+XCTMain(tests)