控制 SwiftUI 中 TextEditor 的大小

18
我想在我的ForEach中添加一些TextEditor,并且我在下面编写了这个示例代码!正如您可以在图像中看到的那样,TextEditor就像一个贪婪的视图,占据了所有可用的空间!对我来说,这有很多缺点!
如果我限制高度为自定义值,那么我将无法看到TextEditor中的所有字符串和字符串行,并且我必须上下滚动以查看其他行,这不是我的设计!
我的目标是TextEditor根据需要占用空间,如果我输入新的字符串行,则可以增加高度;如果我删除字符串行,则可以缩小高度至少为1行!
我想知道如何实现这一点?
struct ContentView: View {
    
    @StateObject var textEditorReferenceType: TextEditorReferenceType = TextEditorReferenceType()
    
    var body: some View {
        
        Text("TextEditorView").bold().padding()
        
        VStack {
            
            ForEach(textEditorReferenceType.arrayOfString.indices, id: \.self, content: { index in
                
                TextEditorView(string: $textEditorReferenceType.arrayOfString[index])

            })
            
        }
        .padding()
        
    }
}


struct TextEditorView: View {
    
    @Binding var string: String
    
    var body: some View {
        
        TextEditor(text: $string)
            .cornerRadius(10.0)
            .shadow(radius: 1.0)
        
    }
    
}

class TextEditorReferenceType: ObservableObject {
    
    @Published var arrayOfString: [String] = ["Hello, World!", "Hello, World!", "Hello, World!"]
    
}

当前代码的结果:

enter image description here

我的目标结果:

enter image description here


2
这个回答解决了你的问题吗 https://dev59.com/T7zpa4cB1Zd3GeqPDxSX#64357649?还有这个也可能有帮助 https://dev59.com/g1IG5IYBdhLWcg3w2VbM#62621521。 - Asperi
是的,非常感谢。 - ios coder
5个回答

26

您可以使用 PreferenceKey 和一个不可见的 Text 叠加层来测量字符串的尺寸并设置 TextEditor 的框架:


struct TextEditorView: View {
    
    @Binding var string: String
    @State var textEditorHeight : CGFloat = 20
    
    var body: some View {
        
        ZStack(alignment: .leading) {
            Text(string)
                .font(.system(.body))
                .foregroundColor(.clear)
                .padding(14)
                .background(GeometryReader {
                    Color.clear.preference(key: ViewHeightKey.self,
                                           value: $0.frame(in: .local).size.height)
                })
            
            TextEditor(text: $string)
                .font(.system(.body))
                .frame(height: max(40,textEditorHeight))
                .cornerRadius(10.0)
                            .shadow(radius: 1.0)
        }.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
        
    }
    
}


struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}

改编自我在此的另一篇答案:模仿iMessage使用TextEditor进行文本输入的行为


1
不,这就是为什么我们必须使用这样的解决方案。 - jnpdx
2
你认为苹果为什么要把SwiftUI搞得这么糟糕?为什么不能只应用“scaledToFit()”就可以了呢?SwiftUI感觉像是一个做得很差的API...它在很多方面都非常有限和糟糕,真的让我感到很头疼。我不明白为什么苹果会做出这样的东西...然后还把它做得如此糟糕... - scaly
1
这将是一个不错的补充,但请记住,它是一个相对年轻的框架。虽然这可能不是适合价值判断的正确论坛。 - jnpdx
1
我正在寻找这个问题的解决方案,然后我看到jnpdx回答了这个问题,我从来没有这么快地复制粘贴过! - Nat Serrano
1
虽然这个方法有点巧妙,但是在间距等方面非常脆弱。而且在使用动态字体大小时似乎会出现问题。更重要的是,这并不是必需的!看看我刚刚发布的答案吧。它利用了fixedSizelayoutPriority来正确地压缩它。 - Mark A. Donohoe
显示剩余3条评论

23

iOS 16 - 本地支持SwiftUI

iOS 16现在可以使用普通的textField添加axis: .vertical.lineLimit()来实现本地支持。

linelimit定义了文本框扩展的行数。添加一个范围以定义文本框内开始和停止扩展的行数。

enter image description here

WWDC22会议“SwiftUI的新功能” 时间为 17:10


这只是针对iOS的吗?我正在构建一个MacOS应用程序,但axis属性不可用。我有最新的Xcode,所以不确定发生了什么。 - Dan
4
这是不适用于 TextEditor 的,它允许换行等。 - Jordi Bruin
2
这个问题是关于 TextEditor,而不是 TextField - Harshad Kale

5

纯粹简单的SwiftUI方法 (*...并且实际上使用TextEditor,而不是TextField!)

与这里的一些答案相反,正如其他人指出的那样,OP明确要求使用TextEditor,而不是TextField。如果您想在文本输入过程中显式添加换行符,而不仅仅是将单行包装成多行,则需要使用TextEditor

使用TextEditor实现这一点的最简单解决方案需要两个步骤:

  1. TextEditor上使用.fixedSize(horizontal: false, vertical: true) (以确定绝对最小高度)
  2. 在同一个容器中使用具有更高布局优先级的另一个贪婪控件 (将其压缩到该高度!)
这是一个例子,我在一个自动增长的TextEditor下面直接放置了一个Button。我使用Color.clear和无限帧来使其贪婪,然后使用.layoutPriority(1)来使其超过TextEditor控件的贪婪性。那个TextEditor上的fixedSize表示“嘿,你不能把我的文本压缩到零高度,兄弟!!”,因此它只会被压缩到这个程度。如果没有fixedSize,它将会折叠到零高度。
struct TestApp: App {

    static private let initialText = """
        He: Tell me a joke!

        She: Ok... what do you call two crows sitting on a branch?

        He: I dunno, what?

        She: Attempted murder!
        """

    @State private var text: String = Self.initialText

    var body: some Scene {
        WindowGroup {

            VStack(alignment: .leading) {

                TextEditor(text: $text)
                .fixedSize(horizontal: false, vertical: true)

                Button("Test Me") {
                    text = Self.initialText
                }

                Color.clear
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .layoutPriority(1)
            }
            .padding()
        }
    }
}

附注:如上所述,layoutPriority仅影响当前容器中的布局,因此请确保它应用于贪婪控件时,要么是您的TextEditor的直接兄弟(即它们具有相同/直接的父级),要么是TextEditor在布局中位于其下方,而不是上方。
这也意味着,如果您将下面的VStack嵌套在另一个包含其他控件的VStack中,那么外部VStack的子项(包括内部VStack)将再次在其中均匀分布(当然,除非您也对这些控件之一应用了layoutPriority)。
如上所述,我在那里使用了一个明确的贪婪间隔控制:使用带有框架的Color.clear。然而,从技术上讲,这并不是必需的,因为您可以直接向按钮添加一个框架来实现相同的效果。您只需要同时指定适当的alignment值,以确定您希望该按钮出现在框架的结果贪婪区域中的位置。在这里,我们使用.topLeading,所以按钮将直接位于左侧的TextEditor下方。如果您不添加alignment,按钮将位于贪婪区域的中间,因为.center是默认的对齐方式。
话虽如此,我个人更喜欢明确的布局,所以间隔控制是我的选择,但其他人可能更喜欢不需要专门的控制来实现这一点的简单性。
// `alignment: topLeading` in the frame puts the button in the top-left of the 'greedy' area that the frame creates
Button("Test Me") {
    text = Self.initialText
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.layoutPriority(1)


这里是结果的示例...

enter image description here
enter image description here
enter image description here


很抱歉,如屏幕截图所示,Color.clear 也会添加一个我不确定如何移除的空白空间。 - undefined
@aehlke,我认为这是因为颜色具有默认的非零最小高度。尝试将其明确设置为零,看看是否有帮助。或者,只需将间隔器移动到您希望与文本视图“对齐”的所有其他控件下方。基本上,您希望扩展区域的位置。 - undefined

4

对我来说,添加.fixedSize(horizontal: false, vertical: true)和最小高度解决了这个问题。

例如:

TextEditor(text: $myBinding)
           .frame(minHeight: 50)
           .fixedSize(horizontal: false, vertical: true)

这应该是被接受的答案。它简单而且完美地起作用。 - undefined

1
或者你们也可以,你懂的,使用一个表单,伙计们:
struct JustUseAForm: View {
    @State var text1: String = "Enter Text Here"
    @State var text2: String = "Enter Text Here"
    @State var text3: String = "Enter Text Here"

    
    var body: some View {
        Form {
            Group {
                VStack(alignment: .leading) {
                    Spacer(minLength: 8)
                    Text("Comment 1:")
                    TextEditor(text: $text1)
                        .padding(.all, 1.0)
                    
                }
                VStack(alignment: .leading) {
                    Spacer(minLength: 8)
                    Text("Comment 2:")
                    TextEditor(text: $text2)
                        .padding(.all, 1.0)
                }
                VStack(alignment: .leading) {
                    Spacer(minLength: 8)
                    Text("Comment 3:")
                    TextEditor(text: $text3)
                        .padding(.all, 1.0)
                }
            }
            .padding(10)
            .background(Color(.sRGB, red: 0.9, green: 0.9, blue: 0.9, opacity: 0.9))
            .cornerRadius(20)
        }
                    
    }
}
    

例子:

just use a form bro

当然,这意味着你必须接受表单默认的渲染方式,因为就像 SwiftUI 中的 .sheet 和其他大多数东西一样,苹果没有给我们提供自定义外观的方法。你要么喜欢他们给出的,要么想出一个奇怪的解决办法,要么使用 UIKit 实现。
有趣的时刻。
也许在评论中有人可以为我们解释一下为什么 TextEditor 在 Form 内部神奇地正常工作,但在其他地方却不行?或者为什么 scaleToFit() 在 TextEditor 上不能正常工作?或者为什么 lineLimit 在 TextEditor 上不能正常工作?这么多问题,答案却很少,除非你在苹果工作,我猜。

我在代码中没有使用表单! - ios coder
表单对我不起作用... 我仍然得到一个贪婪的文本编辑器。 介意分享一下您的看法所包含的内容吗? - Pete
表单控件有很多问题(例如在 macOS 上不能在其中使用 HStack!)。我强烈建议不要使用它们,而是手动设计布局。至于如何解决这个问题,请查看我刚刚发布的答案。保持布局简单易懂。 - Mark A. Donohoe
根据我的理解,这并不能帮助那些需要根据其内容大小调整视图的情况。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接