我正在构建一个 SwiftUI 应用程序,我想将一个可编辑的 UITextView (包装在 UIViewRepresentable 中)放入 SwiftUI ScrollView 中。原因是我有其他 SwiftUI 内容想要放在文本上方,并且我希望它与文本一起滚动。我需要一个 TextView,因为我想要富文本和工具栏。
我想我可以禁用 UITextView 上的滚动并赋予它无限的高度,但这也赋予它无限的宽度,因此文本会水平滚动屏幕边缘,直到添加换行符。我尝试将文本视图的内容大小和框架大小设置为屏幕宽度,如如何在 UITextView 中禁用垂直滚动? 等帖子中建议的那样,但我无法使其工作。我可以成功更新 textview 内容大小,但更改似乎稍后会被覆盖(请参阅 textViewDidChange 中的打印语句)。
有什么办法可以实现这一点吗?即 SwiftUI ScrollView 内的可编辑 UITextView。预先感谢!
import SwiftUI
struct TextView: UIViewRepresentable {
var attributedString: NSAttributedString
var fixedWidth: CGFloat
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
print("pre content size update: \(textView.contentSize)") // width is over limit
let newSize = textView.sizeThatFits(CGSize(width: parent.fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textView.contentSize = CGSize(width: max(newSize.width, parent.fixedWidth), height: newSize.height)
print("post content size update: \(textView.contentSize)") // width is updated
}
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView(frame: .zero)
textView.isEditable = true
textView.isScrollEnabled = false
textView.backgroundColor = .cyan // just to make it easier to see
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.attributedText = self.attributedString
}
}
struct ContentView: View {
var body: some View {
GeometryReader { geo in
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/){
//Other content
Image(systemName: "photo")
.font(.largeTitle)
Button("A button"){
print("i'm a button")
}
//Editable textview
TextView(attributedString: NSAttributedString(string: "Here is a long string as you can see it is not wrapping but instead scrolling off the edge of the screen"), fixedWidth: geo.size.width)
.frame(width: geo.size.width)
.frame(maxHeight: .infinity)
}
.padding(.horizontal)
}
}
}
}
我尝试过的其他一些事情:
为文本视图设置宽度约束,并在视图顶部设置
GeometryReader
。
之所以将
GeometryReader
设置在顶部,是因为在滚动视图内它不允许完全滚动。
UIViewRepresentable Text View
struct TextView: UIViewRepresentable {
var attributedString: NSAttributedString
var fixedWidth: CGFloat
let textView = UITextView(frame: .zero)
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
}
func makeUIView(context: Context) -> UITextView {
textView.isEditable = true
textView.isScrollEnabled = false
textView.backgroundColor = .cyan // just to make it easier to see
textView.delegate = context.coordinator
textView.translatesAutoresizingMaskIntoConstraints = false
textView.widthAnchor.constraint(equalToConstant: fixedWidth).isActive = true //<--- Here
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
DispatchQueue.main.async { //<--- Here
textView.attributedText = self.attributedString
}
}
}
ContentView
struct ContentView: View {
@State private var textViewSize: CGSize = CGSize()
var body: some View {
GeometryReader { geo in //<--- Here
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 0){
//Other content
Image(systemName: "photo")
.font(.largeTitle)
Button("A button"){
print("i'm a button")
}
TextView(attributedString: NSAttributedString(string: "Here is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is \n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new lineinstead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screen ------end"), fixedWidth: geo.size.width - 15 * 2) //<--- Here minus the padding for both size
}.padding(.horizontal, 15)
}
}
}
}
下面的代码解决了这个问题: ScrollView中的UITextView(NSAttributedString)在输入最后一行时不断改变滚动位置(已解决,仍然是一个新问题)
struct TextView: UIViewRepresentable {
var attributedString: NSAttributedString
var fixedWidth: CGFloat
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: .infinity))
if textView.frame.size != size {
textView.frame.size = size
}
}
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView(frame: .zero)
textView.isEditable = true
textView.isScrollEnabled = false
textView.backgroundColor = .cyan // just to make it easier to see
textView.delegate = context.coordinator
textView.translatesAutoresizingMaskIntoConstraints = false
textView.widthAnchor.constraint(equalToConstant: fixedWidth).isActive = true
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.attributedText = self.attributedString
}
}
关键方法:
func textViewDidChange(_ textView: UITextView) {
let size = textView.sizeThatFits(CGSize(width:
textView.frame.size.width, height: .infinity))
if textView.frame.size != size {
textView.frame.size = size
}
}