Skip to content

Instantly share code, notes, and snippets.

@art-divin
Created April 3, 2021 16:29
Show Gist options
  • Save art-divin/eea7acf7e35a2cf148af4492b7d794be to your computer and use it in GitHub Desktop.
Save art-divin/eea7acf7e35a2cf148af4492b7d794be to your computer and use it in GitHub Desktop.
SwiftUI String tokenizer
import SwiftUI
struct TokenColor {
var text : Color
var background : Color
}
extension String : Identifiable {
public var id: Self {
UUID().uuidString
}
}
extension String : View {
static let digitColors : [TokenColor] = [.init(text: .black, background: .yellow)]
static let wordColors : [TokenColor] = [.init(text: .white, background: .blue)]
static let urlColors : [TokenColor] = [.init(text: .blue, background: .white)]
func randomColor(from colors: [TokenColor]) -> TokenColor {
colors.randomElement()!
}
public var body: some View {
switch Token(rawValue: self) {
case .digit(let string):
Text(string)
.foregroundColor(self.randomColor(from: Self.digitColors).text)
.background(
RoundedRectangle(cornerRadius: 5)
.foregroundColor(self.randomColor(from: Self.digitColors).background)
)
case .word(let string):
Text(string)
.foregroundColor(self.randomColor(from: Self.wordColors).text)
.background(
RoundedRectangle(cornerRadius: 5)
.foregroundColor(self.randomColor(from: Self.wordColors).background)
)
case .url(let string):
Text(string)
.foregroundColor(self.randomColor(from: Self.urlColors).text)
.background(
RoundedRectangle(cornerRadius: 5)
.foregroundColor(self.randomColor(from: Self.urlColors).background)
)
default:
Text(self)
}
}
}
enum Token {
case digit(String)
case word(String)
case url(String)
init?(rawValue: String) {
if let digits = rawValue.matches(regex: #"[0-9].+"#) {
if let digitRange = digits.map(\.range).first, let digits = rawValue.string(from: digitRange) {
self = .digit(digits)
} else {
self = .word(rawValue)
}
} else if let url = rawValue.urlMatches() {
if let linkRange = url.map(\.range).first, let url = rawValue.string(from: linkRange) {
self = .url(url)
} else {
self = .word(rawValue)
}
} else {
self = .word(rawValue)
}
}
}
extension String {
func urlMatches() -> [NSTextCheckingResult]? {
guard let regex = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let totalRange = NSRange(location: 0, length: self.count)
let matches = regex.matches(in: self, options: [], range: totalRange)
return matches.isEmpty ? nil : matches
}
func matches(regex: String) -> [NSTextCheckingResult]? {
guard let percentRegex = try? NSRegularExpression(pattern: regex) else {
return nil
}
let totalRange = NSRange(location: 0, length: self.count)
let matches = percentRegex.matches(in: self, options: [], range: totalRange)
return matches.isEmpty ? nil : matches
}
func firstMatch(regex: String) -> NSTextCheckingResult? {
let found = self.matches(regex: regex)
guard let first = found?.first else {
return nil
}
return first
}
func groupMatch(regex: String, group: String) -> Range<String.Index>? {
guard let found = self.firstMatch(regex: regex) else {
return nil
}
return Range(found.range(withName: group), in: self)
}
func string(from range: NSRange) -> String? {
let startIndex = self.startIndex
let offsetIndex = self.index(startIndex, offsetBy: range.location)
let endIndex = self.index(offsetIndex, offsetBy: range.length - 1)
return String(self[offsetIndex ... endIndex])
}
}
struct ContentView: View {
@State var text : String
@State private var strings : [String] = []
var body: some View {
VStack {
ScrollView(.horizontal) {
ScrollViewReader { proxy in
HStack {
ForEach(self.strings) {
$0.id($0)
}
}
.onChange(of: self.strings) {
proxy.scrollTo($0.last, anchor: .trailing)
}
}
}
TextEditor(text: self.$text)
.border(Color.black, width: 1)
}
.onChange(of: self.text) {
self.strings = $0.split(separator: " ").map(String.init)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(text: "")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment