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) | |
} |
I've added the following to support custom fonts:
//private enum SwiftUIFontProvider
case ready(UIFont)
...
//func uiFont() -> UIFont?
case let .ready(font):
return font
...
//init?(from reflection: Any)
case "NamedProvider":
var name: String? = nil
var size: CGFloat? = nil
var textStyle: SwiftUI.Font.TextStyle? = nil
inspect(reflection) { label, value in
switch label {
case "name":
name = value as? String
break
case "size":
size = value as? CGFloat
break
case "textStyle":
textStyle = value as? SwiftUI.Font.TextStyle
break
default:
break
}
}
guard let name, let size else { return nil }
let font = UIFont(name: name, size: size)
guard var font else { return nil }
if let textStyle = textStyle?.toUIFontTextStyle() {
font = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: font)
}
self = .ready(font)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For now it's just a snippet, but this code works on iOS14 🤡
Output: