Hal219
Hal219

Reputation: 81

Using a Geometry Reader within a ScrollView is causing items to overlap

I want to be able to show an array of notes in a ScrollView. Each of these objects contains a GeometryReader, and I'm having trouble setting each of these object's height by the amount of content that is in it.

I've made a reproducible example below (I know in this example it doesn't make sense why I need the geometry reader but I do need it in my actual app).

import SwiftUI

struct ContentView: View {
    var body: some View {
        ScrollView {
            ForEach(notes) { note in
                NoteView(note: note)
            }
        }
    }
}

struct NoteView: View {
    var note: Note = notes[0]
    
    var body: some View{
        GeometryReader { geo in
            Text(note.content)
                .fixedSize(horizontal: false, vertical: true)
                .frame(width: geo.size.width)
        }
    }
}


struct Note: Identifiable {
    var id = UUID()
    var content: String = ""
}

var notes = [
    Note(content: "Lorem ipsum dolor sit amet, consectet adipiscing elit. Condimentum quisque id vitae convallis dignissim pharetra nisl est creatus"),
    Note(content: "Mauris ac tempor libero, non eleifend lectus. Mauris eu hendrerit nunc. Donec at ante mauris. Duis ac elit purus. Mauris ullamcorper mi."),
    Note(content: "Lorem ipsum dolor sit amet, consectet adipiscing elit. Condimentum quisque id vitae convallis dignissim pharetra nisl est creatus"),
    Note(content: "Mauris ac tempor libero, non eleifend lectus. Mauris eu hendrerit nunc. Donec at ante mauris. Duis ac elit purus. Mauris ullamcorper mi.")
]

When I use embed the ForEach loop in a ScrollView all of the items overlap: Items overlapping when using ScrollView

But if I change the ScrollView to a VStack, I get more what I am looking for with the items appearing stacked on top of each other.

VStack allows the correct height of the object

I believe that overlapping is because the height of the GeometryReader is less than the height of the Text within the GeometryReader.

If I manually add .frame(height: 100) to the GeometryReader, the objects no longer overlap. But I do not know how I could create a variable for the height that would be based on the amount of Text each note contains.

Upvotes: 3

Views: 2508

Answers (4)

alexeloswift
alexeloswift

Reputation: 9

I recently ran into a similar issue with GeometryReader and I ended up finding what I think is probably a better solution. Instead of using GeometryReader I used UIScreen.main.bounds.

For example:

You could create an extension of View to retrieve the bounds.

extension View {
    func viewBounds() -> CGRect {
      return UIScreen.main.bounds
    }
}

Having this value you are then able to pass it into your .frame() modifier like this

struct NoteView: View {
    var note: Note = notes[0]
    
    var body: some View{
            Text(note.content)
                .fixedSize(horizontal: false, vertical: true)
                .frame(width: viewBounds().width)
    }
}

I think most likely the GeometryReader doesn't work well with ScrollView because it is meant to take up all the space in a view & since a ScrollView is ever growing it leads to overlapping and just a buggy view in general.

Hope this helps!

Upvotes: 0

Gorkem Sevim
Gorkem Sevim

Reputation: 524

It seems all texts are shown on screen but problem occurs because of GeometryReader.

I have a solution for you. If all texts are shown on screen with their all height we can get that height with adding a background modifier to all of them. And inside that background we can add another GeometryReader to get that background height.

struct NoteView: View {
    let note: Note
    @State private var height: CGFloat = 0
    
    var body: some View{
        GeometryReader { geo in
            Text(note.content)
                .fixedSize(horizontal: false, vertical: true)
                .background {
                    GeometryReader { proxy in
                        Text("")
                            .onAppear {
                                self.height = proxy.size.height
                            }
                    }
                }
        }
        .frame(height: self.height)
    }
}

Upvotes: 1

Al Mustakim
Al Mustakim

Reputation: 559

Try with frame() Example :

Text(myText).frame(height: 100)

Hope it will work.

Edit: i just suggested you to use frame() Height or width fully depends on you. You may use your dynamic value by replacing height:100 Hope this make sense. Thanks

Upvotes: 3

Asperi
Asperi

Reputation: 257711

You don't need GeometryReader for that, it can be done just with .frame capabilities, like

struct NoteView: View {
    var note: Note = notes[0]
    
    var body: some View{
      Text(note.content)
//        .fixedSize(horizontal: false, vertical: true) // this might be not needed as well
        .frame(maxWidth: .infinity)     // << here !!
    }
}

Upvotes: 0

Related Questions