Ios SwiftUI TextEditor初始内容大小错误

Ios SwiftUI TextEditor初始内容大小错误,ios,swiftui,swiftui-texteditor,Ios,Swiftui,Swiftui Texteditor,iOS 14.4+Xcode 12.4 我想在iOS上的SwiftUI中制作一个简单的清单,其中每个项目的文本都是TextEditor 首先,我创建基本的应用程序结构,并用一些演示内容填充它: import SwiftUI @main struct TestApp: App { @State var alpha = "Alpha" @State var bravo = "Bravo is a really long one that should wra

iOS 14.4+Xcode 12.4

我想在iOS上的SwiftUI中制作一个简单的清单,其中每个项目的文本都是
TextEditor

首先,我创建基本的应用程序结构,并用一些演示内容填充它:

import SwiftUI

@main
struct TestApp: App {
  @State var alpha = "Alpha"
  @State var bravo = "Bravo is a really long one that should wrap to multiple lines."
  @State var charlie = "Charlie"
  
  init(){
    //Remove the default background of the TextEditor/UITextView
    UITextView.appearance().backgroundColor = .clear
  }
  
  var body: some Scene {
    WindowGroup {
      ScrollView{
        VStack(spacing: 7){
          TaskView(text: $alpha)
          TaskView(text: $bravo)
          TaskView(text: $charlie)
        }
        .padding(20)
      }
      .background(Color.gray)
    }
  }
}
然后,每个
TaskView
表示列表中的一个任务(白色框):

struct TaskView:View{
  @Binding var text:String
  
  var body: some View{
    HStack(alignment:.top, spacing:8){
      Button(action: {
        print("Test")
      }){
        Circle()
          .strokeBorder(Color.gray,lineWidth: 1)
          .background(Circle().foregroundColor(Color.white))
          .frame(width:22, height: 22)
      }
      .buttonStyle(PlainButtonStyle())
      
      FieldView(name: $text)
       
    }
    .frame(maxWidth: .infinity, alignment: .leading)
    .padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
    .background(Color.white)
    .cornerRadius(5)
  }
}
最后,每个
文本编辑器都位于
字段视图中,如下所示:

struct FieldView: View{
  @Binding var name: String
  var body: some View{
    ZStack{
      Text(name)
        .padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
        .opacity(0)
      TextEditor(text: $name)
        .fixedSize(horizontal: false, vertical: true)
        .padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
    }
  }
}
正如您在上面的屏幕截图中所看到的,文本编辑器的初始高度不会自动调整以适合文本。但我一输入它,它就会适当地调整大小。这里有一段视频显示:

如何使视图具有正确的初始高度?在我输入之前,
TextEditor
会垂直滚动,因此它似乎具有错误的固有内容大小。

注意:视图是半透明的,带有边框,因此您可以查看/调试正在进行的操作

首先,我使用
首选项键
将“不可见”文本视图的高度传递回视图层次结构。然后,我用该值设置
文本编辑器
框架的高度

请注意,视图现在已对齐到
topLeading
——在最初的示例中,不可见文本是居中对齐的

我不喜欢的一件事是边缘插入的使用——这些感觉像是神奇的数字(嗯,它们是…),我宁愿有一个没有它们的解决方案,仍然保持
文本和
文本编辑器
完全对齐。但是,现在这是可行的

使用UIViewRepresentable和UITextView进行更新 这似乎可以避免滚动问题:


struct TaskView:View{
    @Binding var text:String
    @State private var textHeight : CGFloat = 40
    
    var body: some View{
        HStack(alignment:.top, spacing:8){
            Button(action: {
                print("Test")
            }){
                Circle()
                    .strokeBorder(Color.gray,lineWidth: 1)
                    .background(Circle().foregroundColor(Color.white))
                    .frame(width:22, height: 22)
            }
            .buttonStyle(PlainButtonStyle())
            
            FieldView(text: $text, heightToTransmit: $textHeight)
                .frame(height: textHeight)
                .border(Color.red)
            
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
        .background(Color.white)
        .cornerRadius(5)
    }
}

struct FieldView : UIViewRepresentable {
    @Binding var text : String
    @Binding var heightToTransmit: CGFloat
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        let textView = UITextView(frame: .zero, textContainer: nil)
        textView.delegate = context.coordinator
        textView.backgroundColor = .yellow // visual debugging
        textView.isScrollEnabled = false   // causes expanding height
        context.coordinator.textView = textView
        textView.text = text
        view.addSubview(textView)

        // Auto Layout
        textView.translatesAutoresizingMaskIntoConstraints = false
        let safeArea = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
            textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
        ])
        
        return view
    }
    
    func updateUIView(_ view: UIView, context: Context) {
        context.coordinator.heightBinding = $heightToTransmit
        context.coordinator.textBinding = $text
        DispatchQueue.main.async {
            context.coordinator.runSizing()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    class Coordinator : NSObject, UITextViewDelegate {
        var textBinding : Binding<String>?
        var heightBinding : Binding<CGFloat>?
        var textView : UITextView?
        
        func runSizing() {
            guard let textView = textView else { return }
            textView.sizeToFit()
            self.textBinding?.wrappedValue = textView.text
            self.heightBinding?.wrappedValue = textView.frame.size.height
        }
        
        func textViewDidChange(_ textView: UITextView) {
            runSizing()
        }
    }
}

结构任务视图:视图{
@绑定变量文本:字符串
@国家私有变量textHeight:CGFloat=40
var body:一些观点{
HStack(对齐:顶部,间距:8){
按钮(操作:{
打印(“测试”)
}){
圈()
.strokeBorder(颜色.灰色,线宽:1)
.background(圆圈().foregroundColor(颜色.白色))
.框架(宽度:22,高度:22)
}
.buttonStyle(PlainButtonStyle())
FieldView(文本:$text,heightToTransmit:$textHeight)
.frame(高度:textHeight)
.边框(颜色.红色)
}
.frame(最大宽度:。无穷大,对齐:。前导)
.填充(边集(顶部:10,前导:10,底部:10,尾随:30))
.背景(颜色.白色)
.转弯半径(5)
}
}
结构FieldView:UIViewRepresentable{
@绑定变量文本:字符串
@绑定var heightToTransmit:CGFloat
func makeUIView(上下文:context)->UIView{
let view=UIView()
让textView=UITextView(帧:.0,textContainer:nil)
textView.delegate=context.coordinator
textView.backgroundColor=.yellow//可视化调试
textView.IsCrollenabled=false//导致展开高度
context.coordinator.textView=textView
textView.text=文本
view.addSubview(文本视图)
//自动布局
textView.translatesAutoResizezingMaskintoConstraints=false
让safeArea=view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textView.topAnchor.constraint(等式:safeArea.topAnchor),
textView.leadingAnchor.constraint(相等于:安全区域.leadingAnchor),
textView.trailingAnchor.constraint(等式:safeArea.trailingAnchor)
])
返回视图
}
func updateUIView(view:UIView,context:context){
context.coordinator.heightBinding=$heightToTransmit
context.coordinator.textBinding=$text
DispatchQueue.main.async{
context.coordinator.runSizing()文件
}
}
func makeCoordinator()->Coordinator{
返回协调员()
}
类协调器:NSObject、UITextViewDelegate{
var textBinding:绑定?
var-heightBinding:绑定?
var textView:UITextView?
func runSizing(){
guard let textView=textView else{return}
textView.sizeToFit()
self.textBinding?.wrappedValue=textView.text
self.heightBinding?.wrappedValue=textView.frame.size.height
}
func textViewDidChange(textView:UITextView){
运行规模()
}
}
}

这是一个非常巧妙的解决方案,谢谢。我看到的唯一遗留问题是,如果向上或向下滑动
文本编辑器,它仍然会滚动。这里有一个来自模拟器的GIF显示了我的意思:这是使用
文本编辑器
不可避免的副作用吗?我想是的,因为文本编辑器天生就是一个滚动视图,但我可能错了。如果它是UIKit,你可能会禁用滚动,除非它是当前的第一响应者。我曾尝试在
UIExtView
(通过
UIViewRepresentable
)上设置
textView.IsScrolEnabled=false
),但它的行为与UIKit应用程序中的行为不同。我不知道如何使其表现为其边缘将自动布局约束固定到其父对象。请检查我的更新。为我工作。啊,太棒了!我试图做一些类似的事情,但在
updateUIView
中,我不断得到
context.coordinator.textView?.frame.size.height的
0
。这种情况表明SwiftUI有点弱——在UIKit中,这种情况要容易得多。

struct TaskView:View{
    @Binding var text:String
    @State private var textHeight : CGFloat = 40
    
    var body: some View{
        HStack(alignment:.top, spacing:8){
            Button(action: {
                print("Test")
            }){
                Circle()
                    .strokeBorder(Color.gray,lineWidth: 1)
                    .background(Circle().foregroundColor(Color.white))
                    .frame(width:22, height: 22)
            }
            .buttonStyle(PlainButtonStyle())
            
            FieldView(text: $text, heightToTransmit: $textHeight)
                .frame(height: textHeight)
                .border(Color.red)
            
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
        .background(Color.white)
        .cornerRadius(5)
    }
}

struct FieldView : UIViewRepresentable {
    @Binding var text : String
    @Binding var heightToTransmit: CGFloat
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        let textView = UITextView(frame: .zero, textContainer: nil)
        textView.delegate = context.coordinator
        textView.backgroundColor = .yellow // visual debugging
        textView.isScrollEnabled = false   // causes expanding height
        context.coordinator.textView = textView
        textView.text = text
        view.addSubview(textView)

        // Auto Layout
        textView.translatesAutoresizingMaskIntoConstraints = false
        let safeArea = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
            textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
        ])
        
        return view
    }
    
    func updateUIView(_ view: UIView, context: Context) {
        context.coordinator.heightBinding = $heightToTransmit
        context.coordinator.textBinding = $text
        DispatchQueue.main.async {
            context.coordinator.runSizing()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    class Coordinator : NSObject, UITextViewDelegate {
        var textBinding : Binding<String>?
        var heightBinding : Binding<CGFloat>?
        var textView : UITextView?
        
        func runSizing() {
            guard let textView = textView else { return }
            textView.sizeToFit()
            self.textBinding?.wrappedValue = textView.text
            self.heightBinding?.wrappedValue = textView.frame.size.height
        }
        
        func textViewDidChange(_ textView: UITextView) {
            runSizing()
        }
    }
}