1

I wanted to have some TextEditor in my ForEach and I made this sample code in down! As you can see the code and result of it in Image, TextEditor act like a greedy view, and takes all available space! which has so many downsides for me at least!

If I go and limit the hight to a custom value then I would loss the possibility of seeing all strings and lines of strings of TextEditor in itself and I must scroll up or down for seeing other lines, which is not my design!

My goal is that the TextEditor takes the space as it needs and if I enter new line of string then it can grow in height or if I remove the lines of strings it can shrinks in height to minimum of 1 line at least!

I wonder how can I do this?

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!"]
    
}

Result of current code:

enter image description here

Result of my Goal:

enter image description here

swiftPunk
  • 7,543
  • 3
  • 7
  • 30

2 Answers2

6

You can use a PreferenceKey and an invisible Text overlay to measure the string dimensions and set the TextEditor's frame:


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

Adapted from my other answer here: Mimicking behavior of iMessage with TextEditor for text entry

jnpdx
  • 35,475
  • 5
  • 43
  • 61
  • thanks, it works nice, I want to know if TextEditor gave this options for us as native? – swiftPunk Aug 31 '21 at 17:30
  • 1
    No -- that's why we have to use solutions like this. – jnpdx Aug 31 '21 at 17:30
  • Why do you suppose Apple is making SwiftUI so danged screwy? Why can't you just apply "scaledToFit()" and it just works? SwiftUI feels like such a poorly-done API... it is so limited and bad in so many ways that it really hurts my brain. I don't understand why Apple would make something like this... and then make it so crappy... – scaly Jun 02 '22 at 00:13
  • It would be a nice addition, but remember, it's a relatively young framework. This probably isn't the right forum for value judgements, though. – jnpdx Jun 02 '22 at 00:16
0

Or you could just, y'know, use a Form guys:

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

Example:

just use a form bro

Of course, this means you have to be OK with the default way Forms render, because just like .sheet and most other things in SwiftUI, Apple gives us no way to customize the appearance. You either like what they give us, or you figure out a bizarre hack, or you wrap a UIKit implementation.

Fun times.

Maybe in the comments someone can explain for us why TextEditor magically works properly within a Form but not anywhere else? Or why scaleToFit() does not work properly with TextEditor? Or why lineLimit does not work properly with TextEditor? So many questions, so few answers, unless you work at Apple, I guess.

scaly
  • 197
  • 3
  • 14