// // EmojiTextField.swift // ComposeUI // // Created by Shadowfacts on 3/5/23. // import SwiftUI struct EmojiTextField: UIViewRepresentable { typealias UIViewType = UITextField @EnvironmentObject private var controller: ComposeController @Environment(\.colorScheme) private var colorScheme @Binding var text: String let placeholder: String let maxLength: Int? let becomeFirstResponder: Binding? let focusNextView: Binding? init(text: Binding, placeholder: String, maxLength: Int?, becomeFirstResponder: Binding? = nil, focusNextView: Binding? = nil) { self._text = text self.placeholder = placeholder self.maxLength = maxLength self.becomeFirstResponder = becomeFirstResponder self.focusNextView = focusNextView } func makeUIView(context: Context) -> UITextField { let view = UITextField() view.borderStyle = .roundedRect view.font = .preferredFont(forTextStyle: .body) view.adjustsFontForContentSizeCategory = true view.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [ .foregroundColor: UIColor.secondaryLabel, ]) context.coordinator.textField = view view.delegate = context.coordinator view.addTarget(context.coordinator, action: #selector(Coordinator.didChange(_:)), for: .editingChanged) view.addTarget(context.coordinator, action: #selector(Coordinator.returnKeyPressed), for: .primaryActionTriggered) // otherwise when the text gets too wide it starts expanding the ComposeView view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) return view } func updateUIView(_ uiView: UITextField, context: Context) { if text != uiView.text { uiView.text = text } context.coordinator.text = $text context.coordinator.maxLength = maxLength context.coordinator.focusNextView = focusNextView uiView.backgroundColor = colorScheme == .dark ? UIColor(controller.config.fillColor) : .secondarySystemBackground if becomeFirstResponder?.wrappedValue == true { DispatchQueue.main.async { uiView.becomeFirstResponder() becomeFirstResponder!.wrappedValue = false } } } func makeCoordinator() -> Coordinator { Coordinator(controller: controller, text: $text, focusNextView: focusNextView) } class Coordinator: NSObject, UITextFieldDelegate, ComposeInput { let controller: ComposeController var text: Binding var focusNextView: Binding? var maxLength: Int? @Published var autocompleteState: AutocompleteState? var autocompleteStatePublisher: Published.Publisher { $autocompleteState } weak var textField: UITextField? init(controller: ComposeController, text: Binding, focusNextView: Binding?, maxLength: Int? = nil) { self.controller = controller self.text = text self.focusNextView = focusNextView self.maxLength = maxLength } @objc func didChange(_ textField: UITextField) { text.wrappedValue = textField.text ?? "" } @objc func returnKeyPressed() { focusNextView?.wrappedValue = true } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if let maxLength { return ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string).count <= maxLength } else { return true } } func textFieldDidBeginEditing(_ textField: UITextField) { controller.currentInput = self autocompleteState = textField.updateAutocompleteState(permittedModes: .emojis) } func textFieldDidEndEditing(_ textField: UITextField) { controller.currentInput = nil autocompleteState = textField.updateAutocompleteState(permittedModes: .emojis) } func textFieldDidChangeSelection(_ textField: UITextField) { autocompleteState = textField.updateAutocompleteState(permittedModes: .emojis) } // MARK: ComposeInput var toolbarElements: [ToolbarElement] { [.emojiPicker] } func applyFormat(_ format: StatusFormat) { } func beginAutocompletingEmoji() { textField?.insertText(":") } func autocomplete(with string: String) { textField?.autocomplete(with: string, permittedModes: .emojis, autocompleteState: &autocompleteState) } } }