I made this simple uiviewrepresentable which put brackets < and > however sometimes when the text changes ( characters get added ) the cursor jumps up for a split second and back down again. I am new to UIKit and can’t fix it. Help is greatly appreciated.
I tried a lot of solutions but can’t get it to work.
Here is my code for the editor. I will not provide the code for the editor class because that works fine and never caused any issues. However if you need it to get the entire thing working I will gladly provide it.
<code>struct SourceEditor: UIViewRepresentable {
@Binding var text: String
@Binding var highlightText: String
var theme: Theme
func makeUIView(context: Context) -> SourceEditorClass {
let textView = SourceEditorClass()
textView.isEditable = true
textView.delegate = context.coordinator
textView.autocorrectionType = .no
textView.autocapitalizationType = .none
textView.keyboardType = .asciiCapable
textView.font = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)
textView.backgroundColor = UIColor(theme.backgroundColor)
textView.textColor = UIColor(theme.textColor)
textView.textContainer.lineBreakMode = .byWordWrapping
return textView
}
func updateUIView(_ uiView: SourceEditorClass, context: Context) {
context.coordinator.textDidChange = nil
context.coordinator.selectionDidChange = nil
if uiView.text != text {
uiView.text = text
}
if uiView.theme != theme {
uiView.backgroundColor = UIColor(theme.backgroundColor)
uiView.textColor = UIColor(theme.textColor)
uiView.theme = theme
}
context.coordinator.textDidChange = { newText in
text = newText
}
context.coordinator.selectionDidChange = { text, selection in
print(text.lineNumbersForRange(selection) ?? [])
uiView.currentLines = text.lineNumbersForRange(selection) ?? []
highlightText = ""
}
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(theme.textColor), range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.backgroundColor, range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.underlineStyle, range: NSRange(location: 0, length: uiView.text.count))
if !highlightText.isEmpty {
let ranges = findRegex(pattern: highlightText, text: uiView.text)
for range in ranges {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor.yellow.withAlphaComponent(0.8), range: range)
}
}
for keyword in theme.keywords {
let ranges = findRegex(pattern: keyword.regex, text: uiView.text)
for range in ranges {
if keyword.style == .foreground {
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .background {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .underline {
uiView.textStorage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
uiView.textStorage.addAttribute(.underlineColor, value: UIColor(keyword.color), range: range)
}
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator: NSObject, UITextViewDelegate {
var textDidChange: ((String) -> ())? = nil
var selectionDidChange: ((_ text: String, _ selection: NSRange) -> ())? = nil
func textViewDidChange(_ textView: UITextView) {
textDidChange?(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
selectionDidChange?(textView.text, textView.selectedRange)
textView.setNeedsLayout()
}
}
}
</code>
<code>struct SourceEditor: UIViewRepresentable {
@Binding var text: String
@Binding var highlightText: String
var theme: Theme
func makeUIView(context: Context) -> SourceEditorClass {
let textView = SourceEditorClass()
textView.isEditable = true
textView.delegate = context.coordinator
textView.autocorrectionType = .no
textView.autocapitalizationType = .none
textView.keyboardType = .asciiCapable
textView.font = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)
textView.backgroundColor = UIColor(theme.backgroundColor)
textView.textColor = UIColor(theme.textColor)
textView.textContainer.lineBreakMode = .byWordWrapping
return textView
}
func updateUIView(_ uiView: SourceEditorClass, context: Context) {
context.coordinator.textDidChange = nil
context.coordinator.selectionDidChange = nil
if uiView.text != text {
uiView.text = text
}
if uiView.theme != theme {
uiView.backgroundColor = UIColor(theme.backgroundColor)
uiView.textColor = UIColor(theme.textColor)
uiView.theme = theme
}
context.coordinator.textDidChange = { newText in
text = newText
}
context.coordinator.selectionDidChange = { text, selection in
print(text.lineNumbersForRange(selection) ?? [])
uiView.currentLines = text.lineNumbersForRange(selection) ?? []
highlightText = ""
}
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(theme.textColor), range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.backgroundColor, range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.underlineStyle, range: NSRange(location: 0, length: uiView.text.count))
if !highlightText.isEmpty {
let ranges = findRegex(pattern: highlightText, text: uiView.text)
for range in ranges {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor.yellow.withAlphaComponent(0.8), range: range)
}
}
for keyword in theme.keywords {
let ranges = findRegex(pattern: keyword.regex, text: uiView.text)
for range in ranges {
if keyword.style == .foreground {
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .background {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .underline {
uiView.textStorage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
uiView.textStorage.addAttribute(.underlineColor, value: UIColor(keyword.color), range: range)
}
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator: NSObject, UITextViewDelegate {
var textDidChange: ((String) -> ())? = nil
var selectionDidChange: ((_ text: String, _ selection: NSRange) -> ())? = nil
func textViewDidChange(_ textView: UITextView) {
textDidChange?(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
selectionDidChange?(textView.text, textView.selectedRange)
textView.setNeedsLayout()
}
}
}
</code>
struct SourceEditor: UIViewRepresentable {
@Binding var text: String
@Binding var highlightText: String
var theme: Theme
func makeUIView(context: Context) -> SourceEditorClass {
let textView = SourceEditorClass()
textView.isEditable = true
textView.delegate = context.coordinator
textView.autocorrectionType = .no
textView.autocapitalizationType = .none
textView.keyboardType = .asciiCapable
textView.font = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)
textView.backgroundColor = UIColor(theme.backgroundColor)
textView.textColor = UIColor(theme.textColor)
textView.textContainer.lineBreakMode = .byWordWrapping
return textView
}
func updateUIView(_ uiView: SourceEditorClass, context: Context) {
context.coordinator.textDidChange = nil
context.coordinator.selectionDidChange = nil
if uiView.text != text {
uiView.text = text
}
if uiView.theme != theme {
uiView.backgroundColor = UIColor(theme.backgroundColor)
uiView.textColor = UIColor(theme.textColor)
uiView.theme = theme
}
context.coordinator.textDidChange = { newText in
text = newText
}
context.coordinator.selectionDidChange = { text, selection in
print(text.lineNumbersForRange(selection) ?? [])
uiView.currentLines = text.lineNumbersForRange(selection) ?? []
highlightText = ""
}
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(theme.textColor), range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.backgroundColor, range: NSRange(location: 0, length: uiView.text.count))
uiView.textStorage.removeAttribute(.underlineStyle, range: NSRange(location: 0, length: uiView.text.count))
if !highlightText.isEmpty {
let ranges = findRegex(pattern: highlightText, text: uiView.text)
for range in ranges {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor.yellow.withAlphaComponent(0.8), range: range)
}
}
for keyword in theme.keywords {
let ranges = findRegex(pattern: keyword.regex, text: uiView.text)
for range in ranges {
if keyword.style == .foreground {
uiView.textStorage.addAttribute(.foregroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .background {
uiView.textStorage.addAttribute(.backgroundColor, value: UIColor(keyword.color), range: range)
} else if keyword.style == .underline {
uiView.textStorage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
uiView.textStorage.addAttribute(.underlineColor, value: UIColor(keyword.color), range: range)
}
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator: NSObject, UITextViewDelegate {
var textDidChange: ((String) -> ())? = nil
var selectionDidChange: ((_ text: String, _ selection: NSRange) -> ())? = nil
func textViewDidChange(_ textView: UITextView) {
textDidChange?(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
selectionDidChange?(textView.text, textView.selectedRange)
textView.setNeedsLayout()
}
}
}