Created
December 13, 2022 20:42
-
-
Save maximkrouk/e8c2c362715756077e6382ffa0e4ea59 to your computer and use it in GitHub Desktop.
Conversions between UIKit and SwiftUI fonts
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
import UIKit | |
import SwiftUI | |
extension Font { | |
public init(uiFont: UIFont) { | |
self.init(uiFont as CTFont) | |
} | |
public func toUIFont() -> UIFont? { | |
var font: UIFont? | |
inspect(self) { label, value in | |
guard label == "provider" else { return } | |
inspect(value) { label, value in | |
guard label == "base" else { return } | |
guard let provider = SwiftUIFontProvider(from: value) else { | |
return assertionFailure("Could not create font provider") | |
} | |
font = provider.uiFont() | |
} | |
} | |
return font | |
} | |
} | |
private enum SwiftUIFontProvider { | |
case system(size: CGFloat, weight: Font.Weight?, design: Font.Design?) | |
case textStyle(Font.TextStyle, weight: Font.Weight?, design: Font.Design?) | |
case platform(CTFont) | |
func uiFont() -> UIFont? { | |
switch self { | |
case let .system(size, weight, _): | |
return weight?.toUIFontWeight() | |
.map { .systemFont(ofSize: size, weight: $0) } | |
?? .systemFont(ofSize: size) | |
case let .textStyle(textStyle, _, _): | |
return textStyle.toUIFontTextStyle() | |
.map(UIFont.preferredFont(forTextStyle:)) | |
case let .platform(font): | |
return font as UIFont | |
} | |
} | |
init?(from reflection: Any) { | |
switch String(describing: type(of: reflection)) { | |
case "SystemProvider": | |
var props: ( | |
size: CGFloat?, | |
weight: Font.Weight?, | |
design: Font.Design? | |
) = (nil, nil, nil) | |
inspect(reflection) { label, value in | |
switch label { | |
case "size": | |
props.size = value as? CGFloat | |
case "weight": | |
props.weight = value as? Font.Weight | |
case "design": | |
props.design = value as? Font.Design | |
default: | |
return | |
} | |
} | |
guard let size = props.size | |
else { return nil } | |
self = .system( | |
size: size, | |
weight: props.weight, | |
design: props.design | |
) | |
case "TextStyleProvider": | |
var props: ( | |
style: Font.TextStyle?, | |
weight: Font.Weight?, | |
design: Font.Design? | |
) = (nil, nil, nil) | |
inspect(reflection) { label, value in | |
switch label { | |
case "style": | |
props.style = value as? Font.TextStyle | |
case "weight": | |
props.weight = value as? Font.Weight | |
case "design": | |
props.design = value as? Font.Design | |
default: | |
return | |
} | |
} | |
guard let style = props.style | |
else { return nil } | |
self = .textStyle( | |
style, | |
weight: props.weight, | |
design: props.design | |
) | |
case "PlatformFontProvider": | |
var font: CTFont? | |
inspect(reflection) { label, value in | |
guard label == "font" else { return } | |
font = (value as? CTFont?)?.flatMap { $0 } | |
} | |
guard let font else { return nil } | |
self = .platform(font) | |
default: | |
return nil | |
} | |
} | |
} | |
extension Font.TextStyle { | |
fileprivate func toUIFontTextStyle() -> UIFont.TextStyle? { | |
switch self { | |
case .largeTitle: | |
return .largeTitle | |
case .title: | |
return .title1 | |
case .headline: | |
return .headline | |
case .subheadline: | |
return .subheadline | |
case .body: | |
return .body | |
case .callout: | |
return .callout | |
case .footnote: | |
return .footnote | |
case .caption: | |
return .caption1 | |
default: | |
switch self { | |
case .title2: | |
return .title2 | |
case .title3: | |
return .title3 | |
case .caption2: | |
return .caption2 | |
default: | |
assertionFailure() | |
return .body | |
} | |
} | |
} | |
} | |
extension SwiftUI.Font.Weight { | |
fileprivate func toUIFontWeight() -> UIFont.Weight? { | |
var rawValue: CGFloat? = nil | |
inspect(self) { label, value in | |
guard label == "value" else { return } | |
rawValue = value as? CGFloat | |
} | |
guard let rawValue else { return nil } | |
return .init(rawValue) | |
} | |
} | |
private func inspect(_ object: Any, with action: (Mirror.Child) -> Void) { | |
Mirror(reflecting: object).children.forEach(action) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've added the following to support custom fonts: