UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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

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.