2

I've been having an issue with my list where after removing an item, it gets removed correctly, but if I toggle between two branches on the same view, my list items get this weird leading padding.

Not sure how to explain it exactly, so I'm attaching a give of what happens.

https://i.stack.imgur.com/Y0npU.jpg

Here's my list row code:

if (/** condition **/) {
    EmptyView()
} else {
    ZStack(alignment: .center) {
        NavigationLink(
            destination: MovieDetailsView(movie: moviesVM.movie)) {
                EmptyView()
            }.opacity(0)
        WebImage(url: moviesVM.movie.backdropUrl)
            .resizable()
            .placeholder {
                Image("tile_placeholder")
                    .resizable()
                    .aspectRatio(aspectRatioW780Poster, contentMode: .fill)
                    .frame(maxHeight: 170)
                    .clipped()
            }
            .transition(.fade(duration: 0.5))
            .aspectRatio(aspectRatioW780Poster, contentMode: .fill)
            .frame(maxHeight: 170)
            .clipped()
            .overlay(TextOverlay(movieOrShow: moviesVM.movie))
    }
    .swipeActions(edge: .trailing, allowsFullSwipe: false) {
        // buttons that invoke removal
    }
}

And the list rows are instantiated inside the "profile" view, which looks like this:

NavigationView {
    VStack {
        if profileVM.profileRepository.profile == nil {
            HStack(alignment: .center) {
                Text("Please log in or sign up to use your watchlist. ☺️")
            }
        }
        
        if profileVM.profileRepository.profile != nil {
            HStack {
                Text("Watched")
                Toggle("Watched", isOn: $profileVM.defaultTab)
                    .onChange(of: profileVM.defaultTab, perform: { _ in
                        profileVM.updateDefaultTab()
                    })
                    .labelsHidden()
                    .tint(Constants.MUSH_BLUE)
                Text("Watchlist")
            }

            // this is where my view "branches" depending on the toggle
            HStack {
                if !profileVM.defaultTab {
                    ZStack(alignment: .center) {
                        List {
                            ForEach(Array(profileVM.profileRepository.watchedMovies), id: \.id) { movie in
                                MovieListRow(moviesVM: MoviesViewModel(profileRepository: profileVM.profileRepository, movie: movie, fromProfile: true))
                                    .id(movie.id)
                                    .listRowInsets(EdgeInsets())
                                    .listRowSeparator(.hidden)
                            }
                        }
                        .listStyle(InsetListStyle())
                        if profileVM.profileRepository.watchedMovies.count == 0 {
                            Text("Nothing here yet :)")
                        }
                    }
                } else {
                    ZStack(alignment: .center) {
                        List {
                            ForEach(Array(profileVM.profileRepository.watchlistedMovies), id: \.id) { movie in
                                MovieListRow(moviesVM: MoviesViewModel(profileRepository: profileVM.profileRepository, movie: movie, fromProfile: true))
                                    .id(movie.id)
                                    .listRowInsets(EdgeInsets())
                                    .listRowSeparator(.hidden)
                            }
                        }
                        .listStyle(InsetListStyle())
                        if profileVM.profileRepository.watchlistedMovies.count == 0 {
                            Text("Nothing here yet :)")
                        }
                    }
                }
            }
        }
    }
    .navigationTitle("Watchlist")
}

debug view hierarchy

debug view

Vedran Kopanja
  • 1,259
  • 12
  • 23
  • and this only happens after removing an item? – ChrisR Feb 19 '22 at 20:29
  • @ChrisR yes, if I toggle between "Watched" and "Watchlist" before removing any items, it doesn't add the padding (or whatever it does). – Vedran Kopanja Feb 19 '22 at 20:31
  • why do you have `.listStyle(InsetListStyle())` and not `.plain`? And what is about `aspectRatioW780Poster`? – ChrisR Feb 19 '22 at 20:33
  • @ChrisR I have the list style because I wanted the rows to be full width (I just tried with plain, and the issue was the same). And the aspect ratio is just an aspect ratio I use for the poster images, I tried removing that as well - same thing. I will upload a debug view in my OP in a minute. – Vedran Kopanja Feb 19 '22 at 20:42
  • @ChrisR another weird thing is that my NavigationLink gets disabled when this happens – Vedran Kopanja Feb 19 '22 at 20:49
  • try explicit setting of `.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))`. together with `listStyle'(.plain)` – ChrisR Feb 19 '22 at 21:07
  • @ChrisR same result :( – Vedran Kopanja Feb 19 '22 at 21:27
  • I'm running out of ideas. problem is I can't test the full code... what is if you take out the switch between the 2 views – meaning: does it also happen in one view only? – ChrisR Feb 19 '22 at 21:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/242183/discussion-between-vedran-kopanja-and-chrisr). – Vedran Kopanja Feb 19 '22 at 21:34
  • @ChrisR when I switch to TabView the switching works fine, without any padding being added, but then I have a tab bar on the bottom, a step in the right direction, but still not there :/ – Vedran Kopanja Feb 19 '22 at 22:01

1 Answers1

1

Here are a few ideas:

  1. moviesVM - we don't use view model objects in SwiftUI. For transient view data we use the View struct with @State and @Binding which makes the struct value type actually behave like an object because when the struct is init again it automatically has the same property values as last time it was init. We do use an object for managing the lifetime of our model data structs or arrays, in an @Published property in an ObservableObject which we pass into Views using environmentObject.
  2. ForEach(Array( - The ForEach View needs to be given your model data to work correctly, which is usually an array of structs conforming to Identifiable. E.g. ForEach($model.movies) $movie in when we want write access.
  3. MovieListRow(moviesVM MoviesViewModel( - it's a mistake to init objects in body like that, that will slow down SwiftUI and cause memory leaks. In body we only init View data structs that are super fast value types. The body func is called repeatedly and after SwiftUI has diffed and updated the labels etc on screen all of these View structs are discarded.
  4. Your body is too large, we are supposed to break it up into small Views with a small number of lets or @States. This is called having a tight invalidation. The reason is SwiftUI features dependency and change tracking and if the body is too large then it won't be efficient. Naming the structs can be tricky given these are based on data and not areas of the screen, start with ContentView1, ContentView2, etc. til you figure out a good naming scheme, e.g. some name Views based on the properties.

I recommend watching WWDC 2020 Data Essentials in SwiftUI to learn proper use of SwiftUI.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • Hey man, thanks for the reply! 1. Everywhere I read, people keep using MVVM, so my entire app is written like that, my VMs are ObservableObject with Published properties. My auth object is passed in as environment object. 2. My Movie model is conforming to Identifiable. 3. I'll try to switch it up, but how would you instantiate them? Replace with bindings? 4. I just split it up using bindings, but the issue remains :(. – Vedran Kopanja Feb 21 '22 at 20:43
  • I think the MVVM people learned it for object-oriented UIKit and are trying hard to apply it SwiftUI instead of learning Swift's structs and SwiftUI's @ State. And perhaps Apple haven't made it clear enough that @ State is for view state and ObservableObject holds the model structs/arrays. Here is an example of how to do it correctly: https://stackoverflow.com/a/70962745/259521 – malhal Feb 21 '22 at 21:22