swiftui-library / scrollview-reactive-header

ScrollView that supports a parallax header image and static overlay.
55 stars 3 forks source link

Unable to use url-image as header background #2

Open Cacauu opened 2 years ago

Cacauu commented 2 years ago

I use the url-image library in one of the apps I'm building to load images from a server into a SwiftUI Image view. I've tried to migrate the view to use the ScrollViewReactiveHeader and put the URLImage into the header part of it - without any success. The view always displays with 0 height, therefore making the ScrollViewReactiveHeader invisible. See below for a snippet that shows how I'm trying to use both libraries together:

var body: some View {
        NavigationView {
            ScrollViewReactiveHeader(header: {
               URLImage(url) { image in
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .scaledToFill()
                        .clipped()
                        .frame(height: 300)
                }
            }, headerOverlay: {
                [...]
            }, body: {
                [...]
            }, configuration: .init(showStatusBar: true, backgroundColor: nil))
        }
    }

I've also tried wrapping the URLImage in a VStack with a fixed height but that didn't help either. I'd be happy if you could provide any hints on what to do or where the problem might be :)

trentguillory commented 2 years ago

Hey, thanks for reaching out. This is an issue that's been previously reported to me (not through Github Issues). From what I've seen, AsyncImage and URLImage report back a nil height to enclosing GeometryReaders, even after their content has loaded. I'm not sure why this is.

I do know that Kingfisher works as expected, though - after loading, the height is accurate and will be displayed properly inside of ScrollViewReactiveHeader. I'll be doing more testing of this myself when I get the chance, but I'm worried that it's outside the scope of our control with this library.

As for your simplified test of wrapping the URLImage in a VStack, I've noticed oddities with that too. The best simple test that works is to use truly static views. Either a rectangle with a set height or an Image with a local asset - these work as expected.

The best workaround I can provide at the moment is to import Kingfisher for remote images. Let me know how that goes!

Cacauu commented 2 years ago

Hey, thank you for getting back to this! I only now got around to test Kingfisher. Sadly, it still doesn't work as expected. I have tried the ScrollViewReactiveHeader with static images (just normal Image in SwiftUI, no other library) with no problem, but anything that loads from remote sources doesn't seem to work. I have once again added the Kingfisher code to this. Another probably helpful Information is that, if I pass an empty body to the ScrollViewReactiveHeader, it loads the header background and displays it centered in the View.

I was also wondering if this might be related to the fact that I'm loading content for the body property after initially showing the view. The body shows a placeholder at first and then switches to a list (as you can see in the code). I hope this helps you to better understand what I'm doing and maybe helps to solve this problem 😊


var body: some View {
        NavigationView {
            ScrollViewReactiveHeader(header: {
                VStack {
                    if let angebot = someOptional {
                        KFImage(angebot.imageURL)
                            .placeholder {
                                Image(systemName: "arrow.2.circlepath.circle")
                                    .font(.largeTitle)
                                    .opacity(0.3)
                            }
                            .retry(maxCount: 3, interval: .seconds(5))
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .scaledToFill()
                            .clipped()
                            .frame(height: 300)
                            .frame(minWidth: 0, maxWidth: .infinity)

                    } else {
                        Image("someImage")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .scaledToFill()
                            .clipped()
                            .frame(height: 300)
                    }
                    Spacer()
                }
                .frame(height: 300)
            }, headerOverlay: {
                VStack(alignment: .leading) {
                    Spacer()
                    Text("\(anotherOptional ?? "Der Eisbus ist für euch unterwegs!")")
                        .font(Font.headline.bold())
                        .multilineTextAlignment(.leading)
                        .padding()
                        .foregroundColor(Color("EisbusStyledText"))
                        .frame(minWidth: 0, maxWidth: .infinity)
                        .materialBackground()
                }
                .frame(height: 300)
                .frame(minWidth: 0, maxWidth: .infinity)
            }, body: {
                    if model.state == .upToDate {
                        VStack {
                            Picker("...", selection: $selectedView) {
                                Text("Eissorten").tag(0)
                                Text("Eisspezialitäten").tag(1)
                            }
                            .pickerStyle(.segmented)
                            .padding(.leading)
                            .padding(.trailing)
                            .padding(.top)
                            if selectedView == 0 {
                                EissortenList(anOptionalArray ?? [], kugelPreis: moreOptionals ?? 99.9, sahnePreis: andMoreOptionals ?? 99.9, bus: bus)
                                    .frame(minHeight: 0, maxHeight: .infinity)
                            } else if selectedView == 1 {
                                SomeList(eisbecher: anotherOptionalArray ?? [])
                            }
                            #if DEBUG
                                Text("Bus: \(bus)")
                            #endif
                        }
                    }
                    else {
                        VStack(alignment: .center) {
                            Spacer()
                            GenericPlaceholder()
                            if self.model.state == .error {
                                Text("Fehler beim Aktualisieren!")
                            } else {
                                Text("Lädt...")
                            }
                        }
                        .padding()
                    }
            }, configuration: .init(showStatusBar: true, backgroundColor: nil))
                .navigationBarItems(leading:
                    Button("Fertig") {
                        DispatchQueue.main.async {
                            self.presentationMode.wrappedValue.dismiss()
                        }
                    })
                    .navigationBarTitle(Text("..."), displayMode: .inline)
        }
    }