Skip to content

Instantly share code, notes, and snippets.

@davidsteppenbeck
Last active May 5, 2023 05:26
Show Gist options
  • Save davidsteppenbeck/a51a55605cc93e696b3cf25066626173 to your computer and use it in GitHub Desktop.
Save davidsteppenbeck/a51a55605cc93e696b3cf25066626173 to your computer and use it in GitHub Desktop.
Attributed strings in Swift: Add color to strings directly in your Localizable.strings files.
import SwiftUI
enum ColorAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey {
enum Value: String, Codable, Hashable {
case red
case orange
case yellow
case green
case mint
case teal
case cyan
case blue
case indigo
case purple
case pink
case brown
case white
case gray
case black
case primary
case secondary
case accent
case clear
}
/// This is the name that should be used to indicate the color in the string.
/// For example, "^[Hello](color: 'blue'), World!".
static let name = "color"
}
protocol ColorAttributeValueProtocol {
/// Provides a foreground color to apply to the applicable text.
static func color(for value: ColorAttribute.Value) -> Color
}
extension ColorAttributeValueProtocol {
static func color(for value: ColorAttribute.Value) -> Color {
switch value {
case .red:
return .red
case .orange:
return .orange
case .yellow:
return .yellow
case .green:
return .green
case .mint:
return .mint
case .teal:
return .teal
case .cyan:
return .cyan
case .blue:
return .blue
case .indigo:
return .indigo
case .purple:
return .purple
case .pink:
return .pink
case .brown:
return .brown
case .white:
return .white
case .gray:
return .gray
case .black:
return .black
case .primary:
return .primary
case .secondary:
return .secondary
case .accent:
return .accentColor
case .clear:
return .clear
}
}
}
struct ColorText<ID: Hashable>: View, Identifiable, ColorAttributeValueProtocol {
// MARK:- Properties
/// The customized attributed string to display.
private let attributedString: AttributedString
var body: some View {
Text(attributedString)
}
// MARK:- Methods
/// Provides an attributed string with the specified color attributes.
private static func annotate(from source: AttributedString) -> AttributedString {
var attrStr = source
for run in attrStr.runs {
guard let attributeValue = run.color else { continue }
attrStr[run.range].foregroundColor = color(for: attributeValue)
}
return attrStr
}
// MARK:- Identifiable
// Documentation inherited from protocol.
let id: ID
// MARK:- Initialization
init(withAttributedString attrStr: AttributedString, id: ID = UUID()) {
self.attributedString = Self.annotate(from: attrStr)
self.id = id
}
init(_ localizedKey: String.LocalizationValue, id: ID = UUID()) {
self.attributedString = Self.annotate(from: AttributedString(localized: localizedKey, including: \.color))
self.id = id
}
}
extension AttributeScopes {
struct ColorAttributeScope: AttributeScope {
let color: ColorAttribute
}
var color: ColorAttributeScope.Type {
ColorAttributeScope.self
}
}
extension AttributeDynamicLookup {
subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeScopes.ColorAttributeScope, T>) -> T {
self[T.self]
}
}
struct ColorText_Previews: PreviewProvider {
static var previews: some View {
ColorText(withAttributedString: AttributedString(localized: "LOCALIZED_STRING_KEY", including: \.color))
.padding()
.background(Color(.systemBackground))
.previewLayout(.sizeThatFits)
ColorText("This is ^[colored](color: 'purple') text!")
.font(.title)
.fontWeight(.heavy)
.previewLayout(.sizeThatFits)
.padding()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment