From f68038a6f3c0c92c0856ff86dfa4fd18c6facebb Mon Sep 17 00:00:00 2001 From: Franklyn Weber Date: Thu, 4 Feb 2021 15:18:41 +0000 Subject: [PATCH] Initial commit - created package & added files --- .gitignore | 5 + .../contents.xcworkspacedata | 7 ++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++ Package.resolved | 16 +++ Package.swift | 27 +++++ README.md | 3 + Sources/FontPicker/FontPicker+Modifiers.swift | 78 +++++++++++++++ Sources/FontPicker/FontPicker.swift | 93 +++++++++++++++++ Sources/FontPicker/Helpers/CustomFont.swift | 99 +++++++++++++++++++ .../Helpers/Font.TextStyle+Extensions.swift | 47 +++++++++ .../Notification.Name+Extensions.swift | 15 +++ .../Helpers/UIFont+PreferredFont.swift | 33 +++++++ Tests/FontPickerTests/FontPickerTests.swift | 14 +++ Tests/FontPickerTests/XCTestManifests.swift | 9 ++ Tests/LinuxMain.swift | 7 ++ 15 files changed, 461 insertions(+) create mode 100644 .gitignore create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/FontPicker/FontPicker+Modifiers.swift create mode 100644 Sources/FontPicker/FontPicker.swift create mode 100644 Sources/FontPicker/Helpers/CustomFont.swift create mode 100644 Sources/FontPicker/Helpers/Font.TextStyle+Extensions.swift create mode 100644 Sources/FontPicker/Helpers/Notification.Name+Extensions.swift create mode 100644 Sources/FontPicker/Helpers/UIFont+PreferredFont.swift create mode 100644 Tests/FontPickerTests/FontPickerTests.swift create mode 100644 Tests/FontPickerTests/XCTestManifests.swift create mode 100644 Tests/LinuxMain.swift 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)