1

My first ever question here at Stack Overflow.

I'm writing a small application for iOS and macOS. Text entry is done via (now) a TextField and a Button. The issue with the TextField is that it's a single line and doesn't allow for multiline text entry. So, I tried using TextEditor instead, but I can either set it up to not grow as more text is added, or it shows up very big to begin with. What I'm saying is that ideally, it would mimic the behavior that the text entry in iMessage has: starts as the same size of a TextField but grows if needed to accommodate a multiline text like a TextEditor.

Here's the code I am using for this view:

var inputView: some View {
        HStack {
            ZStack {
                //tried this here... 
                //TextEditor(text: $taskText)
                TextField("New entry...", text: $taskText, onCommit: { didTapAddTask() })
                    .frame(maxHeight: 35)
                    .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 0))
                    .clipped()
                    .accentColor(.black)
                    .cornerRadius(8)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Text(taskText).opacity(0).padding(.all, 8) 
            }
            
            Button(action: AddNewEntry, label: { Image(systemName: "plus.circle")
                    .imageScale(.large)
                    .foregroundColor(.primary)
                    .font(.title) }).padding(15).foregroundColor(.primary)
        }
    }

Any way of doing this? I tried different approaches found in different questions from other users, but I can't quite figure out.

Here's how it looks:

How the TextEdit looks

I also have tried something like this and played with different values for the .frame:

        TextEditor(text: $taskText)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200)
            .border(Color.primary, width: 1)
            .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 0))

Any help is appreciated.

Thanks.

Oh, and I'm using Xcode 12.5.1 and target is iOS 14.x and macOS Big Sur for now.

EDIT to answer jnpdx. When I add the code from Dynamic TextEditor overlapping with other views this is how it looks, and it does not change dynamically.

How it looks when the app launches

How it looks when you type text

Aleph
  • 465
  • 2
  • 12
  • This post shows how to make a dynamic-height `TextEditor`: https://stackoverflow.com/a/66487347/560942 – jnpdx Jul 27 '21 at 15:53
  • Thank you @jnpdx. I saw that post already. what I get is a rectangle with which black borders and that grow. I can't figure out a way to attach a screenshot to this comment... – Aleph Jul 27 '21 at 17:35
  • @jnpdx please see images in https://i.stack.imgur.com/03Oqh.png and https://i.stack.imgur.com/G2G0C.png – Aleph Jul 27 '21 at 17:39
  • The border is coming from the `.background(Color.black)` modifier on `TextEditor`. Remove that and you won't see the borders. You have to play with the padding values to get the sizes to be exactly the same. – jnpdx Jul 27 '21 at 17:44
  • Yes, I understand that, but it does NOT grow as more text gets added. It remains the same size, and the TextEditor just moves up as more text is added... – Aleph Jul 27 '21 at 19:06
  • I copied and pasted my example into Xcode and it grows when typing on iOS 14.5. If it is not growing for you, there must be code that was modified that is affecting it. – jnpdx Jul 27 '21 at 19:08
  • The only modification is that I have no `Text` on top, and that I don't use a `Scrollview` – Aleph Jul 27 '21 at 19:19
  • I just removed the `Text` on top and the surrounding `ScrollView` and it continued to work fine for me. Are you sure you kept the `onPreferenceChange` modifier? – jnpdx Jul 27 '21 at 19:22
  • Yes, and in fact adding that is what's causing all the issues. If I remove it it somewhat works, but it doesn't change the height. – Aleph Jul 27 '21 at 19:31
  • Not reproducible for me. Good luck! – jnpdx Jul 27 '21 at 19:33
  • Running that code stand alone I can see why you said it work. It sort of does, but when I add any kind of control to it, it stops working. in any case, on your original code, try keep on adding text. It passes the bottom of the screens and keeps on going and going... it grows beyond the boundaries. – Aleph Jul 27 '21 at 19:37
  • The code I am using in in the question. Maybe I'm missing something. I am a new swift coder (I'm a C programmer....), if you could make that code I posted work with this, it'd be a huge help... I can't quite do it and I don't understand why. – Aleph Jul 27 '21 at 19:38
  • What do you mean by "make that code I posted work with this"? Do you just mean adding the button? Because other than that, it's basically the same. In terms of boundaries, you'd have to define what those are. You could do some logic testing whether to use the height of the text or whatever arbitrary boundary you want. – jnpdx Jul 27 '21 at 19:41
  • I meant, I can't make this particular code you pointed at (https://stackoverflow.com/a/66487347/560942) with the code I posted in the question. No, it's not just a button. Even if I change my entire code for this view, and I add your code, I can't make this work. Not when I try to place it as the edit box in the code I posted, not when I add it stand alone, not when I change `.frame', heights, width, add or remove buttons, add or remove scrollviews, and just try to get this code to work any any capacity that can essentially reproduce how the text entry work on iMessage, which was my question – Aleph Jul 27 '21 at 19:52
  • You haven't shown your "entire code for this view". All you've shown is `inputView`. I've added an answer that utilizes your button from the original code and tried to mimic the structure of the iMessage view. – jnpdx Jul 27 '21 at 19:54

1 Answers1

3

Here's an example, using your original code with the Button next to the TextEditor. The TextEditor grows until it hits the limit, defined by maxHeight. It also has a view for the messages (since you mentioned iMessage), but you could easily remove that.

struct ContentView: View {
    @State private var textEditorHeight : CGFloat = 100
    @State private var text = "Testing text. Hit a few returns to see what happens"
    
    private var maxHeight : CGFloat = 250
    
    var body: some View {
        VStack {
            VStack {
                Text("Messages")
                Spacer()
            }
            Divider()
            HStack {
                ZStack(alignment: .leading) {
                    Text(text)
                        .font(.system(.body))
                        .foregroundColor(.clear)
                        .padding(14)
                        .background(GeometryReader {
                            Color.clear.preference(key: ViewHeightKey.self,
                                                   value: $0.frame(in: .local).size.height)
                        })
                    
                    TextEditor(text: $text)
                        .font(.system(.body))
                        .padding(6)
                        .frame(height: min(textEditorHeight, maxHeight))
                        .background(Color.black)
                }
                .padding(20)
                Button(action: {}) {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                        .foregroundColor(.primary)
                        .font(.title)
                }.padding(15).foregroundColor(.primary)
            }.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
        }
    }
}


struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94