I made this simple UiViewRepresentable which displays line numbers and a super simple html syntax, (I am not going into detail here) but I wondered how I can make it so if I enter: “<” then the editor automatically enters: “>” and the cursors goes back one character. I am very new to UIKit and barely got this one working so help is nearly appreciated.
Here’s the editor 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
// Add toolbar
let toolbar = UIToolbar()
toolbar.sizeToFit()
let lessThanButton = UIBarButtonItem(title: "<", style: .plain, target: context.coordinator, action: #selector(context.coordinator.insertLessThan))
let greaterThanButton = UIBarButtonItem(title: ">", style: .plain, target: context.coordinator, action: #selector(context.coordinator.insertGreaterThan))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolbar.items = [flexibleSpace, lessThanButton, greaterThanButton, flexibleSpace]
textView.inputAccessoryView = toolbar
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(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: SourceEditor
var textDidChange: ((String) -> ())? = nil
var selectionDidChange: ((_ text: String, _ selection: NSRange) -> ())? = nil
init(_ parent: SourceEditor) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
textDidChange?(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
selectionDidChange?(textView.text, textView.selectedRange)
textView.setNeedsLayout()
}
@objc func insertLessThan() {
insertText("<")
}
@objc func insertGreaterThan() {
insertText(">")
}
private func insertText(_ text: String) {
if let textView = UIResponder.currentFirstResponder as? UITextView {
if let selectedRange = textView.selectedTextRange {
textView.replace(selectedRange, withText: text)
}
}
}
}
}
Please note that this code won’t run on its own because of the other requirements in the file. If you need it to fully test your code I can gladly give the full code including all files which are required to run it. Thanks in advance!