自适应 TextView
的实现。
import SwiftUI
fileprivate struct CustomUITextView: UIViewRepresentable {
typealias UIViewType = UITextView
@Binding var text: String
@Binding var height: CGFloat?
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = .zero
textView.backgroundColor = .clear
textView.font = .preferredFont(forTextStyle: .body)
textView.showsVerticalScrollIndicator = false
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
let width = uiView.frame.size.width
let newSize = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
if newSize.height != height {
DispatchQueue.main.async {
self.height = newSize.height
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, height: $height)
}
}
extension CustomUITextView {
final class Coordinator: NSObject, UITextViewDelegate {
@Binding var text: String
@Binding var height: CGFloat?
init(text: Binding<String>, height: Binding<CGFloat?>) {
self._text = text
self._height = height
}
func textViewDidChange(_ textView: UITextView) {
// 中文输入未完成时,直接返回
if textView.markedTextRange?.isEmpty == false { return }
self.text = textView.text
let width = textView.frame.size.width
let newSize = textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
if newSize.height != height {
DispatchQueue.main.async {
self.height = newSize.height
}
}
}
}
}
struct TextView: View {
private let placeholder: LocalizedStringKey
private let maxHeight: CGFloat
@Binding private var text: String
@State private var height: CGFloat?
init(_ placeholder: LocalizedStringKey, text: Binding<String>, maxHeight: CGFloat) {
self.placeholder = placeholder
self.maxHeight = maxHeight
self._text = text
}
var body: some View {
ZStack(alignment: .topLeading) {
if text.isEmpty {
Text(placeholder)
.foregroundColor(Color.primary.opacity(0.25))
}
CustomUITextView(text: $text, height: $height)
.frame(height: (height ?? 0) < maxHeight ? height : maxHeight)
}
}
}