// // AttachmentDescriptionTextView.swift // ComposeUI // // Created by Shadowfacts on 3/12/23. // import SwiftUI struct AttachmentDescriptionTextView: View { @Binding private var text: String private let placeholder: Text? private let minHeight: CGFloat @State private var height: CGFloat? init(text: Binding, placeholder: Text?, minHeight: CGFloat) { self._text = text self.placeholder = placeholder self.minHeight = minHeight } var body: some View { ZStack(alignment: .topLeading) { if text.isEmpty, let placeholder { placeholder .font(.body) .foregroundColor(.secondary) .offset(x: 4, y: 8) } WrappedTextView( text: $text, textDidChange: self.textDidChange, font: .preferredFont(forTextStyle: .body) ) .frame(height: height ?? minHeight) } } private func textDidChange(_ textView: UITextView) { height = max(minHeight, textView.contentSize.height) } } private struct WrappedTextView: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String let textDidChange: ((UITextView) -> Void) let font: UIFont @Environment(\.isEnabled) private var isEnabled func makeUIView(context: Context) -> UITextView { let view = UITextView() view.delegate = context.coordinator view.backgroundColor = .clear view.font = font view.adjustsFontForContentSizeCategory = true view.textContainer.lineBreakMode = .byWordWrapping return view } func updateUIView(_ uiView: UITextView, context: Context) { uiView.text = text uiView.isEditable = isEnabled context.coordinator.textView = uiView context.coordinator.text = $text context.coordinator.didChange = textDidChange // wait until the next runloop iteration so that SwiftUI view updates have finished and // the text view knows its new content size DispatchQueue.main.async { self.textDidChange(uiView) } } func makeCoordinator() -> Coordinator { Coordinator(text: $text, didChange: textDidChange) } class Coordinator: NSObject, UITextViewDelegate, TextViewCaretScrolling { weak var textView: UITextView? var text: Binding var didChange: (UITextView) -> Void var caretScrollPositionAnimator: UIViewPropertyAnimator? init(text: Binding, didChange: @escaping (UITextView) -> Void) { self.text = text self.didChange = didChange super.init() NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil) } @objc private func keyboardDidShow() { guard let textView, textView.isFirstResponder else { return } ensureCursorVisible(textView: textView) } func textViewDidChange(_ textView: UITextView) { text.wrappedValue = textView.text didChange(textView) ensureCursorVisible(textView: textView) } } }