2

I have data loaded into an HStack that is in a Scroll View in SwiftUI. Right now I have it coded where a user can tap on one of those items and have it selected. I'd like for the 0th item to already be selected upon load.

import SwiftUI
import Combine
import Contentful

struct moviesView : View {

    @ObservedObject var fetcher = MovieFetcher()

    @State var selectMovie: Movie? = nil

    @Binding var show: Bool

    var body: some View {
        HStack(alignment: .bottom) {
            if show {
                ScrollView(.horizontal) {
                    Spacer()

                    HStack(alignment: .bottom, spacing: 30) {
                        ForEach(fetcher.movies, id: \.self) { item in
                            selectableRow(movie: item, selectMovie: self.$selectMovie)
                        }
                        .onAppear() {
                            self.selectMovie = self.movies.count > 0 ? self.movies.first! : nil
                        }
                    }
                    .frame(minWidth: 0, maxWidth: .infinity)
                }
                .padding(.leading, 46)
                .padding(.bottom, 26)
            }
        }
    }
}

struct selectableRow : View {

    var movie: Movie

    @Binding var selectedMovie: Movie?

    @State var initialImage = UIImage()

    var body: some View {
        ZStack(alignment: .center) {
            if movie == selectedMovie {
                Image("")
                .resizable()
                .frame(width: 187, height: 254)
                .overlay(
                    RoundedRectangle(cornerRadius: 13)
                Image(uiImage: initialImage)
                .resizable()
                .cornerRadius(13.0)
                .frame(width: 182, height: 249)
                .onAppear {
                    let urlString = "\(urlBase)\(self.movie.movieId).png?"
                    guard let url = URL(string: self.urlString) else { return }
                    URLSession.shared.dataTask(with: url) { (data, response, error) in
                        guard let data = data else { return }
                        guard let image = UIImage(data: data) else { return }

                        RunLoop.main.perform {
                            self.initialImage = image
                        }

                    }.resume()
                }
            } else {
                Image(uiImage: initialImage)
                .resizable()
                .cornerRadius(13.0)
                .frame(width: 135, height: 179)
                .onAppear {
                   let urlString = "\(urlBase)\(self.movie.movieId).png?"
                   guard let url = URL(string: self.urlString) else { return }
                    URLSession.shared.dataTask(with: url) { (data, response, error) in
                        guard let data = data else { return }
                        guard let image = UIImage(data: data) else { return }

                        RunLoop.main.perform {
                            self.initialImage = image
                        }

                    }.resume()
                }
            }
        }
        .onTapGesture {
            self.selectedMovie = self.movie
        }
    }
}

EDIT:

I've added the below suggestion but it's still now working properly. Maybe it's where I added the .onAppear?

So when I launch my app I see the 0th item is selected but when I tap on any item the view just reloads but the 0th item always stays selected.

Additional issue:

Also, my @ObservedObject var fetcher = MovieFetcher() in moviesView is called repeatedly.

1 Answer 1

1

Since you haven't given the full working code, I wasn't able to reproduce the issue you've mentioned. However, I'd suggest you move the .onAppear from ForEach to the HStack (see code below).

I couldn't reproduce the issue you specified.

var body: some View {
    HStack(alignment: .bottom) {
        if show {
            ScrollView(.horizontal) {
                Spacer()

                HStack(alignment: .bottom, spacing: 30) {
                    ForEach(fetcher.movies, id: \.self) { item in
                        selectableRow(movie: item, selectedMovie: self.$selectMovie)
                    }
                }
                .frame(minWidth: 0, maxWidth: .infinity)
            }
            .padding(.leading, 46)
            .padding(.bottom, 26)
            .onAppear() {
                self.selectMovie = self.fetcher.movies.count > 0 ? self.fetcher.movies.first! : nil
            }
        }
    }
}

In the struct moviesView, use the below code to auto select the first movie.

.onAppear() {
    self.selectMovie = self.movies.count > 0 ? self.movies.first! : nil
}

Let me know if you have any other questions.

Sign up to request clarification or add additional context in comments.

9 Comments

I get this --- Cannot use instance member 'fetcher' within property initializer; property initializers run before 'self' is available
Thank you so much! I do have another question but it's related to something different. I have posted it in a different question thread. It's related to making my Contentful data an EnvironmentObject so that when a user selects one of these items then the other data will update upon the selection. stackoverflow.com/questions/60662231/…
I do have another question, now when I tap on an item, the view refreshes but the selection stays on the first object and the images just refresh. The new image I select isn't updated properly.
If this behavior is because of .onAppear, then you can check if self.selectMovie == nil and then update it to the first movie.
If movies will be empty you get crash with this. Just in case.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.