I want to support Genmoji input in my SwiftUI TextField or TextEditor, but looking around, it seems there's no SwiftUI only way to do it?
Does this mean we can only support Genmoji through UITextField and UITextView?
If yes, what's the best way to go about it? I currently have this code, but I feel an input lag.
import UIKit
struct IconField: UIViewRepresentable {
@Binding var text: NSAttributedString?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let textField = UITextView()
textField.delegate = context.coordinator
textField.textColor = .label
textField.font = .preferredFont(forTextStyle: .body)
textField.supportsAdaptiveImageGlyph = true
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.backgroundColor = .clear
return textField
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = text
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: IconField
init(_ parent: IconField) {
self.parent = parent
}
func textViewDidChange(_ textField: UITextView) {
if let attributedText = textField.attributedText {
parent.text = attributedText
}
}
// Delegate method to enforce character limit
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Current attributed text
let currentAttributedText = textView.attributedText ?? NSAttributedString(string: "")
// Convert to NSMutableAttributedString to handle modifications
let mutableAttributedText = NSMutableAttributedString(attributedString: currentAttributedText)
// Replace the text in the specified range with the new text
mutableAttributedText.replaceCharacters(in: range, with: text)
// Check if the updated text exceeds the character limit
return mutableAttributedText.string.count <= 1
}
}
}
Answer with text diffing as requested in comment:
import UIKit
struct IconField: UIViewRepresentable {
@Binding var text: NSAttributedString!
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> UITextView {
context.coordinator.textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
context.coordinator.parent = self
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: IconField! {
didSet {
// diffing the text may improve performance
if textView.attributedText != parent.text {
textView.attributedText = parent.text
}
}
}
lazy var textView: UITextView = {
let textField = UITextView()
textField.delegate = self
textField.textColor = .label
textField.font = .preferredFont(forTextStyle: .body)
textField.supportsAdaptiveImageGlyph = true
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.backgroundColor = .clear
return textField
}()
func textViewDidChange(_ textField: UITextView) {
parent.text = textField.attributedText
}
// Delegate method to enforce character limit
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Current attributed text
let currentAttributedText:NSAttributedString = textView.attributedText
// Convert to NSMutableAttributedString to handle modifications
let mutableAttributedText = NSMutableAttributedString(attributedString: currentAttributedText)
// Replace the text in the specified range with the new text
mutableAttributedText.replaceCharacters(in: range, with: text)
// Check if the updated text exceeds the character limit
return mutableAttributedText.string.count <= 1
}
}
}
Note: I'm not sure shouldChangeTextIn
is the correct place to enforce a character limit. E.g. you might want to let them paste in long text and then edit it down to the required length. That would be easier than having them do the editing in another app like say Notes. Also by default SwiftUI does its formatting/validation on submit, i.e. on return press, rather than while editing is happening.