-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a682316
commit 7ccc92e
Showing
2 changed files
with
348 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import SwiftUI | ||
|
||
extension Font.TextStyle { | ||
|
||
/// The UIKit dynamic text style to use for fonts. | ||
public var uiFontTextStyle: 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 .subheadline: | ||
return .subheadline | ||
case .body: | ||
return .body | ||
case .callout: | ||
return .callout | ||
case .footnote: | ||
return .footnote | ||
case .caption: | ||
return .caption1 | ||
case .caption2: | ||
return .caption2 | ||
case .extraLargeTitle: | ||
if #available(iOS 17.0, *) { | ||
return .extraLargeTitle | ||
} else { | ||
return .largeTitle | ||
} | ||
case .extraLargeTitle2: | ||
if #available(iOS 17.0, *) { | ||
return .extraLargeTitle2 | ||
} else { | ||
return .largeTitle | ||
} | ||
@unknown default: | ||
return .body | ||
} | ||
} | ||
} | ||
|
||
extension Font.Weight { | ||
/// The UIKit weight to use for fonts. | ||
public var uiFontWeight: UIFont.Weight { | ||
switch self { | ||
case .ultraLight: | ||
.ultraLight | ||
case .thin: | ||
.thin | ||
case .regular: | ||
.regular | ||
case .medium: | ||
.medium | ||
case .semibold: | ||
.semibold | ||
case .bold: | ||
.bold | ||
case .heavy: | ||
.heavy | ||
case .black: | ||
.black | ||
default: | ||
.regular | ||
} | ||
} | ||
} | ||
|
||
extension Font.Width { | ||
/// AThe UIKit width to use for fonts that have multiple widths. | ||
public var uiFontWidth: UIFont.Width { | ||
switch self { | ||
case .compressed: | ||
.compressed | ||
case .condensed: | ||
.condensed | ||
case .expanded: | ||
.expanded | ||
case .standard: | ||
.standard | ||
default: | ||
.standard | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
import SwiftUI | ||
|
||
// MARK: - TextStyle | ||
|
||
/// A structure that represents a text style. | ||
/// Use this structure to define a text style with a specific font, size, line height, font scaling, letter spacing, text case, and text decoration. | ||
/// You can apply the text style to a view using the `textStyle(_:)` modifier or to a text using the `textStyleText(_:)` modifier if you need to apply a text style to a `Text` specifically. | ||
/// Additionally, you can apply the text style to an attributed string using the `stylize(with:)` method or initialize an attributed string with the text style using the `NSAttributedString` initializer. | ||
/// Example: | ||
/// ```swift | ||
/// extension TextStyle { | ||
/// static let title = TextStyle( | ||
/// fontType: .system(weight: .bold), | ||
/// size: 24, | ||
/// lineHeight: 32, | ||
/// scaling: .relativeTo(textStyle: .title), | ||
/// letter: .relative(percent: 5), | ||
/// textCase: .uppercase, | ||
/// textDecoration: .underline | ||
/// ) | ||
/// } | ||
/// | ||
/// struct ExampleView: View { | ||
/// var body: some View { | ||
/// Text("Title") | ||
/// .textStyle(.title) | ||
/// } | ||
/// } | ||
/// ``` | ||
/// - Note: The `textStyleText(_:)` modifier does not apply line spacing, text case, or vertical padding based on line height. | ||
/// - Note: The `stylize(with:)` method does not apply line spacing, text case, or vertical padding based on line height. | ||
/// - Note: The `NSAttributedString` initializer does not apply line spacing, text case, or vertical padding based on line height. | ||
public struct TextStyle { | ||
|
||
/// An enumeration that represents a type of font for a text style. | ||
public enum FontType { | ||
|
||
/// A custom font specified by its name. | ||
/// - Parameter name: The name of the font to be used. | ||
case custom(name: String) | ||
|
||
/// A system font specified by its weight and width. | ||
/// - Parameters: | ||
/// - weight: The weight of the system font. | ||
/// - width: The width of the system font. Default is `.standard`. | ||
case system(weight: Font.Weight, width: Font.Width = .standard) | ||
} | ||
|
||
/// An enumeration that represents a type of font scaling for a text style. | ||
public enum FontScaling { | ||
|
||
/// The default font scaling using default font metrics object. | ||
case `default` | ||
|
||
/// The fixed size font scaling without any scaling. | ||
case fixedSize | ||
|
||
/// The font scaling relative to the specified text style. | ||
/// - Parameter textStyle: The text style to which the font scaling is relative. | ||
case relativeTo(textStyle: Font.TextStyle) | ||
|
||
/// The font metrics object for the specified font scaling. | ||
public var fontMetrics: UIFontMetrics? { | ||
switch self { | ||
case .default: | ||
.default | ||
case .fixedSize: | ||
nil | ||
case .relativeTo(let textStyle): | ||
.init(forTextStyle: textStyle.uiFontTextStyle) | ||
} | ||
} | ||
} | ||
|
||
/// An enumeration that represents a type of letter spacing for a text style. | ||
public enum Letter { | ||
|
||
/// The relative letter spacing expressed as a percentage of the font size. | ||
/// - Parameter percent: The percentage of the font size for the relative letter spacing. | ||
case relative(percent: CGFloat) | ||
|
||
/// The absolute letter spacing expressed in points. | ||
/// - Parameter points: The points for the absolute letter spacing. | ||
case absolute(points: CGFloat) | ||
} | ||
|
||
/// An enumeration that represents a type of text decoration for a text style. | ||
public enum TextDecoration: String { | ||
|
||
/// The underline text decoration. | ||
case underline | ||
|
||
/// The strikethrough text decoration. | ||
case strikethrough | ||
} | ||
|
||
/// The type of font for the text style. | ||
public let fontType: FontType | ||
|
||
/// The size of the font for the text style. | ||
public let size: CGFloat | ||
|
||
/// The line height of the font for the text style. | ||
public let lineHeight: CGFloat | ||
|
||
/// The font scaling for the text style. | ||
public let scaling: FontScaling | ||
|
||
/// The letter spacing for the text style. | ||
public let letter: Letter | ||
|
||
/// The text case for the text style. | ||
public let textCase: Text.Case? | ||
|
||
/// The text decoration for the text style. | ||
public let textDecoration: TextDecoration? | ||
|
||
/// The line spacing of the font for the text style. | ||
public var lineSpacing: CGFloat { | ||
(scaling.fontMetrics?.scaledValue(for: lineHeight) ?? lineHeight) - (scaling.fontMetrics?.scaledValue(for: uiFont.lineHeight) ?? uiFont.lineHeight) | ||
} | ||
|
||
/// The font for the text style. | ||
public var font: Font { | ||
switch fontType { | ||
case .custom(let name): | ||
switch scaling { | ||
case .default: | ||
.custom(name, size: size) | ||
case .fixedSize: | ||
.custom(name, fixedSize: size) | ||
case .relativeTo(let textStyle): | ||
.custom(name, size: size, relativeTo: textStyle) | ||
} | ||
case .system(let weight, let width): | ||
.system(size: scaling.fontMetrics?.scaledValue(for: size) ?? size, weight: weight).width(width) | ||
} | ||
} | ||
|
||
/// The `UIFont` for the text style. | ||
public var uiFont: UIFont { | ||
switch fontType { | ||
case .custom(let name): | ||
let font = UIFont(name: name, size: size) ?? .systemFont(ofSize: size) | ||
return scaling.fontMetrics?.scaledFont(for: font) ?? font | ||
case .system(let weight, let width): | ||
return .systemFont( | ||
ofSize: scaling.fontMetrics?.scaledValue(for: size) ?? size, | ||
weight: weight.uiFontWeight, | ||
width: width.uiFontWidth | ||
) | ||
} | ||
} | ||
|
||
/// The kerning of the font for the text style. | ||
public var kerning: CGFloat { | ||
switch letter { | ||
case .relative(let percent): | ||
(scaling.fontMetrics?.scaledValue(for: lineHeight) ?? lineHeight) * (percent / 100.0) | ||
case .absolute(let pixels): | ||
scaling.fontMetrics?.scaledValue(for: pixels) ?? pixels | ||
} | ||
} | ||
|
||
/// Initializes a text style with the specified properties. | ||
/// - Parameters: | ||
/// - fontType: The type of font for the text style. | ||
/// - size: The size of the font for the text style. | ||
/// - lineHeight: The line height of the font for the text style. | ||
/// - scaling: The font scaling for the text style. Default is `.default`. | ||
/// - letter: The letter spacing for the text style. Default is `.absolute(points: 0)`. | ||
/// - textCase: The text case for the text style. Default is `nil`. | ||
/// - textDecoration: The text decoration for the text style. Default is `nil`. | ||
public init( | ||
fontType: FontType, | ||
size: CGFloat, | ||
lineHeight: CGFloat, | ||
scaling: FontScaling = .default, | ||
letter: Letter = .absolute(points: 0), | ||
textCase: Text.Case? = nil, | ||
textDecoration: TextDecoration? = nil | ||
) { | ||
self.fontType = fontType | ||
self.size = size | ||
self.lineHeight = lineHeight | ||
self.scaling = scaling | ||
self.letter = letter | ||
self.textCase = textCase | ||
self.textDecoration = textDecoration | ||
} | ||
} | ||
|
||
// MARK: - Extensions | ||
|
||
extension View { | ||
/// Applies the specified text style to the view. | ||
/// - Parameter style: The text style to apply to the view. | ||
/// - Returns: A view that applies the specified text style. | ||
public func textStyle(_ style: TextStyle) -> some View { | ||
self | ||
.font(style.font) | ||
.lineSpacing(style.lineSpacing) | ||
.kerning(style.kerning) | ||
.underline(style.textDecoration == .underline) | ||
.strikethrough(style.textDecoration == .strikethrough) | ||
.textCase(style.textCase) | ||
.padding(.vertical, style.lineSpacing / 2) | ||
} | ||
} | ||
|
||
extension Text { | ||
/// Applies the specified text style to the text. | ||
/// Use this modifier when you need to apply a text style to a `Text`, rather than to a generic View. | ||
/// - Parameter style: The text style to apply to the text. | ||
/// - Returns: A text that applies the specified text style. | ||
/// - Note: This modifier does not apply line spacing, text case, or vertical padding based on line height. | ||
public func textStyleText(_ style: TextStyle) -> Text { | ||
self | ||
.font(style.font) | ||
.kerning(style.kerning) | ||
.underline(style.textDecoration == .underline) | ||
.strikethrough(style.textDecoration == .strikethrough) | ||
} | ||
} | ||
|
||
extension AttributedStringProtocol { | ||
/// Applies the specified text style to the attributed string. | ||
/// - Parameter style: The text style to apply to the attributed string. | ||
/// - Returns: An attributed string that applies the specified text style. | ||
/// - Note: This method does not apply line spacing, text case, or vertical padding based on line height. | ||
public mutating func stylize(with style: TextStyle) { | ||
self.font = style.font | ||
self.kern = style.kerning | ||
self.underlineStyle = style.textDecoration == .underline ? .single : nil | ||
self.strikethroughStyle = style.textDecoration == .strikethrough ? .single : nil | ||
} | ||
} | ||
|
||
extension NSAttributedString { | ||
/// Initializes an attributed string with the specified properties. | ||
/// - Parameters: | ||
/// - string: The string for the attributed string. | ||
/// - textStyle: The text style for the attributed string. | ||
/// - additionalAttributes: The additional attributes for the attributed string. Default is `nil`. | ||
public convenience init( | ||
string: String, | ||
textStyle: TextStyle, | ||
additionalAttributes: [NSAttributedString.Key: Any]? = nil | ||
) { | ||
self.init( | ||
string: string, | ||
attributes: [ | ||
.font: textStyle.uiFont, | ||
.kern: textStyle.kerning, | ||
] | ||
) | ||
} | ||
} |