Introduces TextEditor backport to iOS 14+
This commit is contained in:
parent
afac0c36c4
commit
b092912f24
|
@ -0,0 +1,223 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
protocol ColorProvider {
|
||||||
|
var color: UIColor? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct AccentColorProvider: ColorProvider {
|
||||||
|
var color: UIColor? {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
return .tintColor
|
||||||
|
} else {
|
||||||
|
return UIColor(Color.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct TintShapeStyle: ColorProvider {
|
||||||
|
var color: UIColor? {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
return .tintColor
|
||||||
|
} else {
|
||||||
|
return UIColor(Color.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct ForegroundStyle: ColorProvider {
|
||||||
|
var color: UIColor? { .label }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct BackgroundStyle: ColorProvider {
|
||||||
|
var color: UIColor? { .systemBackground }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct UICachedDeviceRGBColor: ColorProvider {
|
||||||
|
var color: UIColor?
|
||||||
|
|
||||||
|
init(provider: Any) {
|
||||||
|
let mirror = Mirror(reflecting: provider)
|
||||||
|
let red = mirror.descendant("linearRed") as? Float ?? 1
|
||||||
|
let green = mirror.descendant("linearGreen") as? Float ?? 1
|
||||||
|
let blue = mirror.descendant("linearBlue") as? Float ?? 1
|
||||||
|
let opacity = mirror.descendant("opacity") as? Float ?? 1
|
||||||
|
let cgColor = CGColor(
|
||||||
|
colorSpace: .init(name: CGColorSpace.genericRGBLinear) ?? CGColorSpaceCreateDeviceRGB(),
|
||||||
|
components: [.init(red), .init(green), .init(blue), .init(opacity)]
|
||||||
|
)
|
||||||
|
color = cgColor.flatMap { UIColor(cgColor: $0) } ?? .label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct UIDynamicCatalogSystemColor: ColorProvider {
|
||||||
|
var color: UIColor?
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct DisplayP3: ColorProvider {
|
||||||
|
var color: UIColor?
|
||||||
|
|
||||||
|
init(provider: Any) {
|
||||||
|
let mirror = Mirror(reflecting: provider)
|
||||||
|
let red = mirror.descendant("red") as? CGFloat ?? 1
|
||||||
|
let green = mirror.descendant("green") as? CGFloat ?? 1
|
||||||
|
let blue = mirror.descendant("blue") as? CGFloat ?? 1
|
||||||
|
let opacity = mirror.descendant("opacity") as? Float ?? 1
|
||||||
|
let cgColor = CGColor(
|
||||||
|
colorSpace: .init(name: CGColorSpace.displayP3) ?? CGColorSpaceCreateDeviceRGB(),
|
||||||
|
components: [.init(red), .init(green), .init(blue)]
|
||||||
|
)
|
||||||
|
color = cgColor.flatMap { UIColor(cgColor: $0).withAlphaComponent(.init(opacity)) } ?? .label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct OffsetShapeStyle<T: ColorProvider>: ColorProvider {
|
||||||
|
var color: UIColor?
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
extension OffsetShapeStyle<SystemColorsStyle> {
|
||||||
|
init(provider: Any) {
|
||||||
|
let mirror = Mirror(reflecting: provider)
|
||||||
|
let offset = mirror.descendant("offset") as? Int ?? 0
|
||||||
|
switch offset {
|
||||||
|
case 1: color = .secondaryLabel
|
||||||
|
case 2: color = .tertiaryLabel
|
||||||
|
case 3: color = .quaternaryLabel
|
||||||
|
default: color = .label
|
||||||
|
}
|
||||||
|
print(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct SelectionShapeStyle: ColorProvider {
|
||||||
|
var color: UIColor? { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct SystemColorsStyle: ColorProvider {
|
||||||
|
let style: SystemColorType.Style
|
||||||
|
var color: UIColor? { style.color }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct SystemColorType: ColorProvider {
|
||||||
|
enum Style: String {
|
||||||
|
case primary, secondary
|
||||||
|
case black, white, gray, clear
|
||||||
|
case blue, brown, cyan, green
|
||||||
|
case indigo, mint, orange, pink
|
||||||
|
case purple, red, teal, yellow
|
||||||
|
|
||||||
|
var color: UIColor {
|
||||||
|
switch self {
|
||||||
|
case .black: return .black
|
||||||
|
case .white: return .white
|
||||||
|
case .primary: return .label
|
||||||
|
case .secondary: return .secondaryLabel
|
||||||
|
case .blue: return .systemBlue
|
||||||
|
case .brown: return .systemBrown
|
||||||
|
case .clear: return .clear
|
||||||
|
case .cyan:
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
return .systemCyan
|
||||||
|
} else {
|
||||||
|
return .systemTeal
|
||||||
|
}
|
||||||
|
case .gray: return .systemGray
|
||||||
|
case .green: return .systemGreen
|
||||||
|
case .indigo: return .systemIndigo
|
||||||
|
case .mint:
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
return .systemMint
|
||||||
|
} else {
|
||||||
|
return .systemTeal
|
||||||
|
}
|
||||||
|
case .orange: return .systemOrange
|
||||||
|
case .pink: return .systemPink
|
||||||
|
case .purple: return .systemPurple
|
||||||
|
case .red: return .systemRed
|
||||||
|
case .teal: return .systemTeal
|
||||||
|
case .yellow: return .systemYellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let style: Style
|
||||||
|
var color: UIColor? { style.color }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func colorProvider(from values: EnvironmentValues) -> Any? {
|
||||||
|
let mirror = Mirror(reflecting: values)
|
||||||
|
guard let provider = mirror.descendant(
|
||||||
|
"_plist", "elements", "some", "value",
|
||||||
|
"some", "storage", "box", "base"
|
||||||
|
) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func isAccentColor(provider: Any) -> Bool {
|
||||||
|
String(describing: type(of: provider)) == String(describing: AccentColorProvider.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func resolveColor(_ values: EnvironmentValues) -> UIColor? {
|
||||||
|
guard let provider = colorProvider(from: values) else { return nil }
|
||||||
|
return resolveColorProvider(provider)?.color
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func resolveColorProvider(_ provider: Any) -> ColorProvider? {
|
||||||
|
switch String(describing: type(of: provider)) {
|
||||||
|
case String(describing: SelectionShapeStyle.self):
|
||||||
|
return SelectionShapeStyle()
|
||||||
|
case String(describing: AccentColorProvider.self):
|
||||||
|
return AccentColorProvider()
|
||||||
|
case String(describing: TintShapeStyle.self):
|
||||||
|
return TintShapeStyle()
|
||||||
|
case String(describing: ForegroundStyle.self):
|
||||||
|
return ForegroundStyle()
|
||||||
|
case String(describing: BackgroundStyle.self):
|
||||||
|
return BackgroundStyle()
|
||||||
|
case String(describing: OffsetShapeStyle<SystemColorsStyle>.self):
|
||||||
|
return OffsetShapeStyle<SystemColorsStyle>(provider: provider)
|
||||||
|
case String(describing: SystemColorType.self):
|
||||||
|
return SystemColorType(style: .init(rawValue: "\(provider)") ?? .primary)
|
||||||
|
case String(describing: SystemColorsStyle.self):
|
||||||
|
return SystemColorsStyle(style: .init(rawValue: "\(provider)") ?? .primary)
|
||||||
|
case String(describing: UICachedDeviceRGBColor.self):
|
||||||
|
return UICachedDeviceRGBColor(provider: provider)
|
||||||
|
case String(describing: UIDynamicCatalogSystemColor.self):
|
||||||
|
return UIDynamicCatalogSystemColor()
|
||||||
|
case String(describing: DisplayP3.self):
|
||||||
|
return DisplayP3(provider: provider)
|
||||||
|
case "Resolved":
|
||||||
|
return UICachedDeviceRGBColor(provider: provider)
|
||||||
|
default:
|
||||||
|
print("Unhandled color provider: \(String(describing: type(of: provider)))")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printMirror(_ value: Any) {
|
||||||
|
let mirror = Mirror(reflecting: value)
|
||||||
|
print(mirror.subjectType)
|
||||||
|
for child in mirror.children {
|
||||||
|
print(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,301 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
protocol FontProvider {
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
extension FontProvider {
|
||||||
|
func font(with traitCollection: UITraitCollection?) -> UIFont {
|
||||||
|
return UIFont(descriptor: fontDescriptor(with: traitCollection), size: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
protocol FontModifier {
|
||||||
|
func modify(_ fontDescriptor: inout UIFontDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
protocol StaticFontModifier: FontModifier {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct TextStyleProvider: FontProvider {
|
||||||
|
var style: UIFont.TextStyle
|
||||||
|
var design: UIFontDescriptor.SystemDesign?
|
||||||
|
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor {
|
||||||
|
UIFont
|
||||||
|
.preferredFont(forTextStyle: style, compatibleWith: traitCollection)
|
||||||
|
.fontDescriptor
|
||||||
|
.withDesign(design ?? .default)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct SystemProvider: FontProvider {
|
||||||
|
var size: CGFloat
|
||||||
|
var design: UIFontDescriptor.SystemDesign
|
||||||
|
var weight: UIFont.Weight?
|
||||||
|
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor {
|
||||||
|
UIFont.systemFont(ofSize: size)
|
||||||
|
.fontDescriptor
|
||||||
|
.addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.weight: (weight ?? .regular).rawValue
|
||||||
|
]
|
||||||
|
])
|
||||||
|
.withDesign(design)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct NamedProvider: FontProvider {
|
||||||
|
var name: String
|
||||||
|
var size: CGFloat
|
||||||
|
var textStyle: UIFont.TextStyle?
|
||||||
|
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor {
|
||||||
|
if let textStyle = textStyle {
|
||||||
|
let metrics = UIFontMetrics(forTextStyle: textStyle )
|
||||||
|
|
||||||
|
return UIFontDescriptor(fontAttributes: [
|
||||||
|
.family: name,
|
||||||
|
.size: metrics.scaledValue(for: size, compatibleWith: traitCollection)
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
return UIFontDescriptor(fontAttributes: [
|
||||||
|
.family: name,
|
||||||
|
.size: size
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct StaticModifierProvider<M: StaticFontModifier>: FontProvider {
|
||||||
|
var base: FontProvider
|
||||||
|
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor {
|
||||||
|
var descriptor = base.fontDescriptor(with: traitCollection)
|
||||||
|
M().modify(&descriptor)
|
||||||
|
return descriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct ModifierProvider<M: FontModifier>: FontProvider {
|
||||||
|
var base: FontProvider
|
||||||
|
var modifier: M
|
||||||
|
|
||||||
|
func fontDescriptor(with traitCollection: UITraitCollection?) -> UIFontDescriptor {
|
||||||
|
var descriptor = base.fontDescriptor(with: traitCollection)
|
||||||
|
modifier.modify(&descriptor)
|
||||||
|
return descriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct ItalicModifier: StaticFontModifier {
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
func modify(_ fontDescriptor: inout UIFontDescriptor) {
|
||||||
|
var traits = fontDescriptor.symbolicTraits
|
||||||
|
traits.insert(.traitItalic)
|
||||||
|
fontDescriptor = fontDescriptor.addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.symbolic: traits.rawValue
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct BoldModifier: StaticFontModifier {
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
func modify(_ fontDescriptor: inout UIFontDescriptor) {
|
||||||
|
var traits = fontDescriptor.symbolicTraits
|
||||||
|
traits.insert(.traitBold)
|
||||||
|
fontDescriptor = fontDescriptor.addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.symbolic: traits.rawValue,
|
||||||
|
UIFontDescriptor.TraitKey.weight: nil
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct WeightModifier: FontModifier {
|
||||||
|
var weight: UIFont.Weight?
|
||||||
|
|
||||||
|
func modify(_ fontDescriptor: inout UIFontDescriptor) {
|
||||||
|
fontDescriptor = fontDescriptor.addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.weight: (weight ?? .regular).rawValue
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct LeadingModifier: FontModifier {
|
||||||
|
var leading: Font.Leading?
|
||||||
|
|
||||||
|
func modify(_ fontDescriptor: inout UIFontDescriptor) {
|
||||||
|
var traits = fontDescriptor.symbolicTraits
|
||||||
|
switch leading {
|
||||||
|
case .loose:
|
||||||
|
traits.insert(.traitLooseLeading)
|
||||||
|
traits.remove(.traitTightLeading)
|
||||||
|
case .tight:
|
||||||
|
traits.remove(.traitLooseLeading)
|
||||||
|
traits.insert(.traitTightLeading)
|
||||||
|
default:
|
||||||
|
traits.remove(.traitLooseLeading)
|
||||||
|
traits.remove(.traitTightLeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
fontDescriptor = fontDescriptor.addingAttributes([
|
||||||
|
.traits: [
|
||||||
|
UIFontDescriptor.TraitKey.symbolic: traits.rawValue
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func resolveFont(_ font: Font) -> FontProvider? {
|
||||||
|
let mirror = Mirror(reflecting: font)
|
||||||
|
|
||||||
|
guard let provider = mirror.descendant("provider", "base") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveFontProvider(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
func resolveFontProvider(_ provider: Any) -> FontProvider? {
|
||||||
|
let mirror = Mirror(reflecting: provider)
|
||||||
|
|
||||||
|
switch String(describing: type(of: provider)) {
|
||||||
|
case String(describing: TextStyleProvider.self):
|
||||||
|
guard let style = mirror.descendant("style") as? Font.TextStyle else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let design = mirror.descendant("design") as? Font.Design
|
||||||
|
return TextStyleProvider(style: style.uiTextStyle, design: design?.uiSystemDesign)
|
||||||
|
case String(describing: StaticModifierProvider<ItalicModifier>.self):
|
||||||
|
guard let base = mirror.descendant("base", "provider", "base") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveFontProvider(base).map(StaticModifierProvider<ItalicModifier>.init)
|
||||||
|
case String(describing: StaticModifierProvider<BoldModifier>.self):
|
||||||
|
guard let base = mirror.descendant("base", "provider", "base") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveFontProvider(base).map(StaticModifierProvider<BoldModifier>.init)
|
||||||
|
case String(describing: ModifierProvider<WeightModifier>.self):
|
||||||
|
guard let base = mirror.descendant("base", "provider", "base") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let weight = mirror.descendant("modifier", "weight") as? Font.Weight
|
||||||
|
let modifier = WeightModifier(weight: weight?.uiFontWeight)
|
||||||
|
return resolveFontProvider(base).map { ModifierProvider(base: $0, modifier: modifier) }
|
||||||
|
case String(describing: ModifierProvider<LeadingModifier>.self):
|
||||||
|
guard let base = mirror.descendant("base", "provider", "base") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let leading = mirror.descendant("modifier", "leading") as? Font.Leading
|
||||||
|
let modifier = LeadingModifier(leading: leading)
|
||||||
|
return resolveFontProvider(base).map { ModifierProvider(base: $0, modifier: modifier) }
|
||||||
|
case String(describing: SystemProvider.self):
|
||||||
|
guard let size = mirror.descendant("size") as? CGFloat,
|
||||||
|
let design = mirror.descendant("design") as? Font.Design else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let weight = mirror.descendant("weight") as? Font.Weight
|
||||||
|
|
||||||
|
return SystemProvider(size: size, design: design.uiSystemDesign, weight: weight?.uiFontWeight)
|
||||||
|
case String(describing: NamedProvider.self):
|
||||||
|
guard let name = mirror.descendant("name") as? String,
|
||||||
|
let size = mirror.descendant("size") as? CGFloat else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let textStyle = mirror.descendant("textStyle") as? Font.TextStyle
|
||||||
|
|
||||||
|
return NamedProvider(name: name, size: size, textStyle: textStyle?.uiTextStyle)
|
||||||
|
default:
|
||||||
|
// Not exhaustive, more providers need to be handled here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Font.Weight {
|
||||||
|
var uiFontWeight: UIFont.Weight {
|
||||||
|
switch self {
|
||||||
|
case .ultraLight: return .ultraLight
|
||||||
|
case .light: return .light
|
||||||
|
case .thin: return .thin
|
||||||
|
case .medium: return .medium
|
||||||
|
case .semibold: return .semibold
|
||||||
|
case .bold: return .bold
|
||||||
|
case .heavy: return .heavy
|
||||||
|
case .black: return .black
|
||||||
|
default: return .regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Font.Design {
|
||||||
|
var uiSystemDesign: UIFontDescriptor.SystemDesign {
|
||||||
|
switch self {
|
||||||
|
case .monospaced: return .monospaced
|
||||||
|
case .rounded: return .rounded
|
||||||
|
case .serif: return .serif
|
||||||
|
default: return .`default`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Font.TextStyle {
|
||||||
|
var uiTextStyle: UIFont.TextStyle {
|
||||||
|
switch self {
|
||||||
|
case .caption: return .caption1
|
||||||
|
case .caption2: return .caption2
|
||||||
|
case .footnote: return .footnote
|
||||||
|
case .callout: return .callout
|
||||||
|
case .subheadline: return .subheadline
|
||||||
|
case .headline: return .headline
|
||||||
|
case .title: return .title1
|
||||||
|
case .title2: return .title2
|
||||||
|
case .title3: return .title3
|
||||||
|
case .largeTitle: return .largeTitle
|
||||||
|
default: return .body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LegibilityWeight {
|
||||||
|
var uiLegibilityWeight: UILegibilityWeight {
|
||||||
|
switch self {
|
||||||
|
case .bold: return .bold
|
||||||
|
default: return .regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,81 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@available(iOS 15, *)
|
||||||
|
extension DynamicTypeSize {
|
||||||
|
var uiContentSizeCategory: UIContentSizeCategory {
|
||||||
|
switch self {
|
||||||
|
case .xSmall: return .extraSmall
|
||||||
|
case .small: return .small
|
||||||
|
case .medium: return .medium
|
||||||
|
case .large: return .large
|
||||||
|
case .xLarge: return .extraLarge
|
||||||
|
case .xxLarge: return .extraExtraLarge
|
||||||
|
case .xxxLarge: return .extraExtraExtraLarge
|
||||||
|
case .accessibility1: return .accessibilityMedium
|
||||||
|
case .accessibility2: return .accessibilityLarge
|
||||||
|
case .accessibility3: return .accessibilityExtraLarge
|
||||||
|
case .accessibility4: return .accessibilityExtraExtraLarge
|
||||||
|
case .accessibility5: return .accessibilityExtraExtraExtraLarge
|
||||||
|
default: return .large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LayoutDirection {
|
||||||
|
var uiLayoutDirection: UITraitEnvironmentLayoutDirection {
|
||||||
|
switch self {
|
||||||
|
case .leftToRight: return .leftToRight
|
||||||
|
case .rightToLeft: return .rightToLeft
|
||||||
|
default: return .leftToRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TextAlignment {
|
||||||
|
var nsTextAlignment: NSTextAlignment {
|
||||||
|
switch self {
|
||||||
|
case .leading: return .left
|
||||||
|
case .center: return .center
|
||||||
|
case .trailing: return .right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ContentSizeCategory {
|
||||||
|
var uiContentSizeCategory: UIContentSizeCategory {
|
||||||
|
switch self {
|
||||||
|
case .extraSmall: return .extraSmall
|
||||||
|
case .small: return .small
|
||||||
|
case .medium: return .medium
|
||||||
|
case .large: return .large
|
||||||
|
case .extraLarge: return .extraLarge
|
||||||
|
case .extraExtraLarge: return .extraExtraLarge
|
||||||
|
case .extraExtraExtraLarge: return .extraExtraExtraLarge
|
||||||
|
case .accessibilityMedium: return .accessibilityMedium
|
||||||
|
case .accessibilityLarge: return .accessibilityLarge
|
||||||
|
case .accessibilityExtraLarge: return .accessibilityExtraLarge
|
||||||
|
case .accessibilityExtraExtraLarge: return .accessibilityExtraExtraLarge
|
||||||
|
case .accessibilityExtraExtraExtraLarge: return .accessibilityExtraExtraExtraLarge
|
||||||
|
default: return .large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var uiTraitCollection: UITraitCollection {
|
||||||
|
var traits: [UITraitCollection] = [
|
||||||
|
.init(legibilityWeight: legibilityWeight?.uiLegibilityWeight ?? .unspecified),
|
||||||
|
.init(layoutDirection: layoutDirection.uiLayoutDirection),
|
||||||
|
]
|
||||||
|
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
traits.append(.init(preferredContentSizeCategory: dynamicTypeSize.uiContentSizeCategory))
|
||||||
|
} else {
|
||||||
|
traits.append(.init(preferredContentSizeCategory: sizeCategory.uiContentSizeCategory))
|
||||||
|
}
|
||||||
|
|
||||||
|
return UITraitCollection(traitsFrom: traits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,137 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
|
||||||
|
extension Backport where Wrapped == Any {
|
||||||
|
|
||||||
|
/// A view that can display and edit long-form text.
|
||||||
|
///
|
||||||
|
/// A text editor view allows you to display and edit multiline, scrollable text in your app’s user interface. By default, the text editor view styles the text using characteristics inherited from the environment, like font(_:), foregroundColor(_:), and multilineTextAlignment(_:).
|
||||||
|
///
|
||||||
|
/// You create a text editor by adding a TextEditor instance to the body of your view, and initialize it by passing in a Binding to a string variable in your app.
|
||||||
|
///
|
||||||
|
/// To style the text, use the standard view modifiers to configure a system font, set a custom font, or change the color of the view’s text.
|
||||||
|
/// In this example, the view renders the editor’s text in gray with a custom font:
|
||||||
|
///
|
||||||
|
/// struct TextEditingView: View {
|
||||||
|
/// @State private var fullText: String = "This is some editable text..."
|
||||||
|
///
|
||||||
|
/// var body: some View {
|
||||||
|
/// TextEditor(text: $fullText)
|
||||||
|
/// .foregroundColor(Color.gray)
|
||||||
|
/// .font(.custom("HelveticaNeue", size: 13))
|
||||||
|
/// .lineSpacing(5)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// > The order of some modifiers matter with this implementation. PLEASE REPORT ISSUES ON THE REPO!
|
||||||
|
///
|
||||||
|
/// Specifically, its recommended to place `foregroundColor` modifiers BEFORE `font` modifiers to ensure things work as expected.
|
||||||
|
///
|
||||||
|
public struct TextEditor: View {
|
||||||
|
@Environment(\.self) private var environment
|
||||||
|
@Binding var text: String
|
||||||
|
|
||||||
|
/// Creates a plain text editor.
|
||||||
|
///
|
||||||
|
/// Use a TextEditor instance to create a view in which users can enter and edit long-form text.
|
||||||
|
/// In this example, the text editor renders gray text using the 13 point Helvetica Neue font with 5 points of spacing between each line:
|
||||||
|
///
|
||||||
|
/// struct TextEditingView: View {
|
||||||
|
/// @State private var fullText: String = "This is some editable text..."
|
||||||
|
///
|
||||||
|
/// var body: some View {
|
||||||
|
/// TextEditor(text: $fullText)
|
||||||
|
/// .foregroundColor(Color.gray)
|
||||||
|
/// .font(.custom("HelveticaNeue", size: 13))
|
||||||
|
/// .lineSpacing(5)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// You can define the styling for the text within the view, including the text color, font, and line spacing. You define these styles by applying standard view modifiers to the view. The default text editor doesn’t support rich text, such as styling of individual elements within the editor’s view. The styles you set apply globally to all text in the view.
|
||||||
|
///
|
||||||
|
/// - Parameter text: A `Binding` to the variable containing the text to edit.
|
||||||
|
public init(text: Binding<String>) {
|
||||||
|
_text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isAccented: Bool {
|
||||||
|
guard let provider = colorProvider(from: environment) else { return false }
|
||||||
|
return isAccentColor(provider: provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Representable(parent: self)
|
||||||
|
.blendMode(!environment.isEnabled && isAccented ? .luminosity: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Representable: UIViewRepresentable {
|
||||||
|
let parent: TextEditor
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
.init(parent: parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
context.coordinator.view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ view: UIView, context: Context) {
|
||||||
|
context.coordinator.update(parent: parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Coordinator: NSObject, UITextViewDelegate {
|
||||||
|
let view = UITextView(frame: .zero)
|
||||||
|
var parent: TextEditor
|
||||||
|
|
||||||
|
init(parent: TextEditor) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(parent: TextEditor) {
|
||||||
|
self.parent = parent
|
||||||
|
guard view.text != parent.text else { return }
|
||||||
|
|
||||||
|
view.delegate = self
|
||||||
|
view.adjustsFontForContentSizeCategory = true
|
||||||
|
view.autocapitalizationType = .allCharacters
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
|
||||||
|
switch parent.environment.disableAutocorrection {
|
||||||
|
case .some(true):
|
||||||
|
view.autocorrectionType = .yes
|
||||||
|
case .some(false):
|
||||||
|
view.autocorrectionType = .no
|
||||||
|
case nil:
|
||||||
|
view.autocorrectionType = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
let style = NSMutableParagraphStyle()
|
||||||
|
style.lineSpacing = parent.environment.lineSpacing
|
||||||
|
style.alignment = parent.environment.multilineTextAlignment.nsTextAlignment
|
||||||
|
|
||||||
|
view.textColor = resolveColor(parent.environment) ?? .label
|
||||||
|
view.font = resolveFont(parent.environment.font ?? .body)?
|
||||||
|
.font(with: parent.environment.uiTraitCollection)
|
||||||
|
?? .preferredFont(forTextStyle: .body)
|
||||||
|
|
||||||
|
view.typingAttributes = [
|
||||||
|
.paragraphStyle: style,
|
||||||
|
.foregroundColor: view.textColor ?? .label,
|
||||||
|
.font: view.font ?? .preferredFont(forTextStyle: .body)
|
||||||
|
]
|
||||||
|
|
||||||
|
view.text = parent.text
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.parent.text = textView.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
Loading…
Reference in New Issue