NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

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()
                }
            }
        }
    }

   

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.

1      

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)
        }
    }
}

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED Building and maintaining in-app subscription infrastructure is hard. Luckily there's a better way. With RevenueCat, you can implement subscriptions for your app in hours, not months, so you can get back to building your app.

Try it for free

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

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.