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.

Luke Irvin
  • 1,179
  • 1
  • 20
  • 39

1 Answers1

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.

Felix Marianayagam
  • 2,634
  • 2
  • 9
  • 29
  • I get this --- Cannot use instance member 'fetcher' within property initializer; property initializers run before 'self' is available – Luke Irvin Mar 13 '20 at 03:07
  • 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. https://stackoverflow.com/questions/60662231/convert-contentful-model-to-environmentobject – Luke Irvin Mar 13 '20 at 03:32
  • 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. – Luke Irvin Mar 13 '20 at 03:49
  • If this behavior is because of .onAppear, then you can check if self.selectMovie == nil and then update it to the first movie. – Felix Marianayagam Mar 13 '20 at 04:23
  • If `movies` will be empty you get crash with this. Just in case. – Asperi Mar 13 '20 at 05:33
  • Still doesn't seem to help. Maybe I have the .onAppear() added to the wrong place? I've added it to the ForEach in moviesView. – Luke Irvin Mar 13 '20 at 15:15
  • So this seems to make the fetcher blow up & get called repeatedly when I have it under the ForEach. But, my data only shows when I have it set up there. So, for example, I also set my title & video trailer for the first item there. But now that the fetcher is blowing up my images don't load. – Luke Irvin Mar 14 '20 at 23:10
  • You need to provide the minimal source code that helps reproduce the issue you are talking about. Without that, I'm unable to help. However, you should be able to move the .onAppear to HStack. See updated answer. – Felix Marianayagam Mar 15 '20 at 05:36
  • @FelixMarianayagam I'll debug some more. This does help the issue of it blowing up, but the selection of the 0th item doesn't happen now. I'll debug a little bit more and if I can't figure it out I'll add additional code. – Luke Irvin Mar 15 '20 at 17:07