How to manage Scrollview Height according to textview content Swiftui

Viewed 502

I am trying to manage scrollview height according to textview content height with other view.

Code:

struct TextviewWithScrollview: View {
    @State private var textStyle = UIFont.TextStyle.body
    @State private var textForTextView = "fdgfhjdsgfdhsgfdsfg dsfg dsfgdsfh fh sf hdsjklf dhsjkfhsdjkfdhsjkfadhsfkds fdshfjkldsh fjkldsh fdshfdshfdsfhsfdsfh sf ewf g iuf herjkdsjkjvdhsvdshvdsv dshv ds vads vds hvsdvjkds vds hviuds  vhv disu ghdisuv g"
    var body: some View {
        ZStack {
            Color.red
                .ignoresSafeArea()
            ScrollView {
                VStack(alignment: .leading) {
                    TextView(isEditable: .constant(false), text:  .constant(textForTextView), textStyle:$textStyle, didStartEditing: .constant(false),placeHolderText:"")
                }
                
                HStack {
                    Button("Top") {
                    }
                    Button("Middle") {
                        
                    }
                    Button("Bottom") {
                    }
                }
            }
        }
    }
}

struct TextView: UIViewRepresentable {
    @Binding var isEditable:Bool
    @Binding var text: String
    @Binding var textStyle: UIFont.TextStyle
    @Binding var didStartEditing: Bool
    var placeHolderText: String = ""
    
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = UIFont.preferredFont(forTextStyle: textStyle)
        textView.autocapitalizationType = .sentences
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.isScrollEnabled = true
        textView.dataDetectorTypes = .all
        textView.textColor = .white
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
        uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator($text)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        
        init(_ text: Binding<String>) {
            self.text = text
        }
        
        func textViewDidChange(_ textView: UITextView) {
            DispatchQueue.main.async {
                self.text.wrappedValue = textView.text
            }
        }
    }
}

Output without Scrollview:

Output Without Scrollview

Output With Scrollview:

Output With Scrollview

Can someone please explain to me how to manage Scrollview Height according to textview content with other's views. I've tried to implement by above but no results yet.

Any help would be greatly appreciated.

Thanks in advance.

2 Answers

If I understand you correctly, this is what you would like to achieve:

enter image description here

To get this result, embed the ScrollView in a GeometryReader and size the TextView with a frame. Like so:

struct TextviewWithScrollview: View {
@State private var textStyle = UIFont.TextStyle.body
@State private var textForTextView = "fdgfhjdsgfdhsgfdsfg dsfg dsfgdsfh fh sf hdsjklf dhsjkfhsdjkfdhsjkfadhsfkds fdshfjkldsh fjkldsh fdshfdshfdsfhsfdsfh sf ewf g iuf herjkdsjkjvdhsvdshvdsv dshv ds vads vds hvsdvjkds vds hviuds  vhv disu ghdisuv g"
var body: some View {
    ZStack {
        Color.red
            .ignoresSafeArea()
        GeometryReader { geo in
            ScrollView {
                VStack(alignment: .leading) {
                    TextView(isEditable: .constant(false), text:  .constant(textForTextView), textStyle:$textStyle, didStartEditing: .constant(false),placeHolderText:"")
                        .frame(width: geo.size.width, height: 300)
                }
                HStack {
                    Button("Top") {
                    }
                    Button("Middle") {
                    }
                    Button("Bottom") {
                    }
                }
            }
        }
    }
}

Here is another solution that adds auto-resizing of the TextView based on the amount of text shown:

enter image description here

And here is the code for that:

class myTextObject: ObservableObject {
    @Published var text: String = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem."
}

struct TextviewWithScrollview: View {
    @State private var textStyle = UIFont.TextStyle.body
    
    @StateObject var textForView = myTextObject()
    @State private var textViewSize: CGSize = CGSize(width: 300, height: 300)
    
    var body: some View {
        ZStack {
            Color.red
                .ignoresSafeArea()
            
            GeometryReader { geo in
                ScrollView {

                    VStack(alignment: .leading) {
                        TextView(
                            isEditable: .constant(false),
                            text: $textForView.text,
                            textStyle: $textStyle,
                            didStartEditing: .constant(false),
                            placeHolderText: "",
                            textViewSize: $textViewSize
                        )
                        .frame(minHeight: 30, idealHeight: min(textViewSize.height, geo.size.height), maxHeight: geo.size.height)
                    }
                    
                    HStack {
                        Button("Top") {
                        }
                        Button("Middle") {
                            self.$textForView.text.wrappedValue += "\n-- a new line --"
                        }
                        Button("Bottom") {
                        }
                    }
                }
            }
        }
    }
}

struct TextView: UIViewRepresentable {
    @Binding var isEditable:Bool
    @Binding var text: String
    @Binding var textStyle: UIFont.TextStyle
    @Binding var didStartEditing: Bool
    var placeHolderText: String = ""
    
    @Binding var textViewSize: CGSize
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = UIFont.preferredFont(forTextStyle: textStyle)
        textView.autocapitalizationType = .sentences
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.isScrollEnabled = true
        textView.dataDetectorTypes = .all
        textView.textColor = .gray
        DispatchQueue.main.async {
            textViewSize = textView.sizeThatFits(textView.textContainer.size)
        }
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
        uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
        
        DispatchQueue.main.async {
            let newContentSize = uiView.sizeThatFits(uiView.textContainer.size)
            context.coordinator.parent.$textViewSize.wrappedValue = newContentSize
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: TextView
        
        init(parent: TextView)  {
            self.parent = parent
        }
        
        func textViewDidChange(_ textView: UITextView) {
            
            DispatchQueue.main.async {
                self.parent.$text.wrappedValue = textView.text
                
                let newContentSize = textView.sizeThatFits(textView.textContainer.size)
                self.parent.$textViewSize.wrappedValue = newContentSize
            }
        }
    }
}

Related