如何在 SwiftUI ScrollView 中设置可编辑 UITextView 的宽度?

问题描述 投票:0回答:2

我正在构建一个 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)
            }
        }
        
    }
}

我尝试过的其他一些事情:

  • 仅使用普通的可滚动文本视图并将我的所有内容作为文本附件放入 NSAttributed 字符串中 -> 对我的内容限制太大并且难以检测用户交互
  • 在 updateUIView() 中的文本视图上设置锚点约束 -> 当文本为空时效果很好,但如果屏幕已经加载文本,则父视图的布局将完全重新排列
  • 将绑定从父级传递到文本视图的高度和宽度,如How to use an NSAttributedString with a ScrollView in SwiftUI? -> 不起作用。我认为这些问题并不像第一次出现那样相似 - 我想为可编辑文本设置固定大小,而这个问题需要为不可编辑文本设置可调整的内容大小
ios swift swiftui uiscrollview uitextview
2个回答
9
投票

为文本视图设置宽度约束,并在视图顶部设置

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)
            }
        }
    }
}


0
投票

下面的代码解决了这个问题: 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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.