2023-04-16 17:23:13 +00:00
|
|
|
//
|
|
|
|
// 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<Bool>?
|
|
|
|
let focusNextView: Binding<Bool>?
|
|
|
|
|
|
|
|
init(text: Binding<String>, placeholder: String, maxLength: Int?, becomeFirstResponder: Binding<Bool>? = nil, focusNextView: Binding<Bool>? = 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
|
|
|
|
}
|
2024-03-09 19:15:14 +00:00
|
|
|
if placeholder != uiView.attributedPlaceholder?.string {
|
|
|
|
uiView.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [
|
|
|
|
.foregroundColor: UIColor.secondaryLabel,
|
|
|
|
])
|
|
|
|
}
|
2023-04-16 17:23:13 +00:00
|
|
|
|
|
|
|
context.coordinator.text = $text
|
|
|
|
context.coordinator.maxLength = maxLength
|
|
|
|
context.coordinator.focusNextView = focusNextView
|
|
|
|
|
2023-11-08 21:52:46 +00:00
|
|
|
#if !os(visionOS)
|
2023-04-16 17:23:13 +00:00
|
|
|
uiView.backgroundColor = colorScheme == .dark ? UIColor(controller.config.fillColor) : .secondarySystemBackground
|
2023-11-08 21:52:46 +00:00
|
|
|
#endif
|
2023-04-16 17:23:13 +00:00
|
|
|
|
|
|
|
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<String>
|
|
|
|
var focusNextView: Binding<Bool>?
|
|
|
|
var maxLength: Int?
|
|
|
|
|
|
|
|
@Published var autocompleteState: AutocompleteState?
|
|
|
|
var autocompleteStatePublisher: Published<AutocompleteState?>.Publisher { $autocompleteState }
|
|
|
|
|
|
|
|
weak var textField: UITextField?
|
|
|
|
|
|
|
|
init(controller: ComposeController, text: Binding<String>, focusNextView: Binding<Bool>?, 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] }
|
|
|
|
|
2023-05-05 14:13:20 +00:00
|
|
|
var textInputMode: UITextInputMode? {
|
|
|
|
textField?.textInputMode
|
|
|
|
}
|
|
|
|
|
2023-04-16 17:23:13 +00:00
|
|
|
func applyFormat(_ format: StatusFormat) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func beginAutocompletingEmoji() {
|
|
|
|
textField?.insertText(":")
|
|
|
|
}
|
|
|
|
|
|
|
|
func autocomplete(with string: String) {
|
|
|
|
textField?.autocomplete(with: string, permittedModes: .emojis, autocompleteState: &autocompleteState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|