WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Automatically update list in Xcode 12

Forums > SwiftUI

Hi there,

Im currently creating an app for an index of sheet music and I wish to filter the songs via favourites. I have the code down and am using ObservableObject data store to change the bool but when I change the toggle button in a detail view the change does not carry over in the list. the list does not automatically change. I beleive its because I'm using ObservedObject rather that EnvironmentalObject. I Thought to use this you need to change SceneDelegate but I created the app in Xcode 12 and there is no SceneDelegate. The toggle changes the isFavourite but doesnt update the list.

see code below:

import Combine
import SwiftUI

final class UserData: NSObject, ObservableObject {
    @Published var showFavoritesOnly = false
    @Published var songs = songData
}
struct ListView: View {

    @ObservedObject var userData = UserData()

    var body: some View {
        NavigationView {
            List {
                ForEach(userData.songs, id: \.id) { song in
                    if !self.userData.showFavoritesOnly || song.isFavorite {
                        NavigationLink(destination: DetailView(data: song)) {
                            RowView(data: song)
                        }
                    }
                }
            }
            .navigationBarTitle("Music Index", displayMode: .inline)
            .navigationBarItems(trailing:
                                    Toggle(isOn: $userData.showFavoritesOnly) {
                                        Image(systemName: "circle.fill")
                                            .foregroundColor(.blue)
                                    })
        }
    }
}
struct DetailView: View {

    var data: DataSection

    @ObservedObject var userData = UserData()

    var songIndex: Int {
        userData.songs.firstIndex(where: { $0.id == data.id })!
    }

        var body: some View {
            ZStack {
                Color(.white)
                VStack {
                    Button(action: {
                        self.userData.songs[self.songIndex]
                            .isFavorite.toggle()
                    }) {
                        Text("Select as favourite")
                            .fontWeight(.heavy)
                            .foregroundColor(.black)
                        if self.userData.songs[self.songIndex]
                            .isFavorite {
                            Image(systemName: "circle.fill")
                                .foregroundColor(Color.blue)
                        } else {
                            Image(systemName: "circle")
                                .foregroundColor(Color.blue)
                        }
                    }.padding(.top)

                    Image(data.songNumber)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .onAppear { UIApplication.shared.isIdleTimerDisabled = true }
                        .onDisappear { UIApplication.shared.isIdleTimerDisabled = false }
                    Spacer()
                }
            }
        }
    }

3      

I beleive its because I'm using ObservedObject rather that EnvironmentalObject.

@ObservedObject should be used when an external source supplies the object to the View.

From Apple's documentation:

SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view. Instead, SwiftUI provides the StateObject attribute for this purpose.

In your case, ListView is creating a new userData on every load because of this line:

@ObservedObject var userData = UserData()

You can use @EnvironmentObject or @StateObject to solve this, depending on if ListView owns this data or not. (It sounds like not, but I don't know the rest of your code.)

I Thought to use this you need to change SceneDelegate but I created the app in Xcode 12 and there is no SceneDelegate.

The .environmentObject() modifier can be used on any View. Add it to the root View in your App class and it will get passed down to your entire app.

4      

Thankyou so much!

I have read and am keeping the page bookmarked for future reference. Im learning everyday with swift development and having people like you that explain it so well and am patient with me as a noob lol. I entered a @StateObject in the main struct and then used the environment modifier.

@main
struct Song_IndexApp: App {

    @StateObject var music = UserData()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(music)
        }
    }
}

3      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.