JuniperPhoton / Widget-Intermediate-Animation

A demo to demonstrate how to animate widget changes with intermediate state
29 stars 0 forks source link

Widget changes not syncing to main app #1

Open codefruitio opened 11 months ago

codefruitio commented 11 months ago

Hello! Thanks for this demo project. I've noticed that when you modify a task through the widget, the change is not reflected in the main app until the app is restarted. I've seen the same behavior in my own apps using Interactive Widgets with SwiftData. Is this just expected behavior for the SwiftData beta currently?

JuniperPhoton commented 11 months ago

Hi. Do you fetch your data when the app is in the foreground like monitoring the ScenePhase and fetch data when its value changes to .foreground? I haven’t encountered this issue before in iOS 17 Beta 6.

codefruitio commented 11 months ago

I'm not doing anything special in the app besides the @query command in the view to pull from the ModelContainer. When I used your app, I had the same behavior though. Where when I completed a task, the main app didnt reflect the changes when brought into the foreground unless I killed and reopened the entire app

JuniperPhoton commented 11 months ago

Oh I reproduce this issue and seems that fetching and saving data manually won't work. However today Apple released the new Beta 8, I haven't have time to try it yet because I am on my trip, can you share the result after upgrading Xcode 15 beta and try?

codefruitio commented 11 months ago

Beta 8 produces the same result, unfortunately. Sent from my iPhoneOn Aug 31, 2023, at 6:46 AM, JuniperPhoton @.***> wrote: Oh I reproduce this issue and seems that fetching and saving data manually won't work. However today Apple released the new Beta 8, I haven't have time to try it yet because I am on my trip, can you share the result after upgrading Xcode 15 beta and try?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: @.***>

JuniperPhoton commented 11 months ago

I think we should fire a feedback to Apple. I tried Core Data and the issue exists and it seems that the changes won't be written to the disk until the app is killed. Also, I can only try on the simulator for now, but as far as I know the simulator has different behaviors regarding to saving UserDefaults to the disk.

I also tried changing the value in UserDefaults and the value is updated as soon as I tap the widget to bring the app to the foreground.

codefruitio commented 11 months ago

Good idea. I've never filed feedback with them before. Is that something you'd like to do? I'd be happy to figure it out otherwise

JuniperPhoton commented 11 months ago

I would. But I find out that the last feedbacks I submitted are still in the “open” state. But it’s still better to file a feedback though.

By the way, did you test on real device or just use the simulator like me?

codefruitio commented 11 months ago

Okay I’ll file it. I’ve tried on a simulator and physical device with the same results. Sent from my iPhoneOn Aug 31, 2023, at 8:14 PM, JuniperPhoton @.***> wrote: I would. But I find out that the last feedbacks I submitted are still in the “open” state. But it’s still better to fire a feedback though. By the way, did you test on real device or just use the simulator like me?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: @.***>

codefruitio commented 11 months ago

I have tested on a simulator and physical device with the same result. Feedback filed.. FB13107596

JuniperPhoton commented 11 months ago

Thanks. I will also report this issue later.

JuniperPhoton commented 11 months ago

I have tried a method to avoid this issue.

When fetching data: use a dedicated ModelContext to fetch data. By default the @Query Marco uses MainContext to fetch data.

let context = ModelContext(sharedModelContainer)
let items = (try? context.fetch(FetchDescriptor<Item>())) ?? []
self.items = items

And it's ok to update the data in an arbitrary context in Widget extension.

The reason I tried this method is that my app MyerList has adopted this interactive widget feature before I make this demo and article and it works as expected. Though it uses Core Data instead of SwiftData, but I always uses the background context to update data and update data using a subscription-notification style.

After all, I think it's still the issue of SwiftData (even in Beta 8). And I will still keep my eye on this issue.

iandundas commented 11 months ago

In my own app this has also been driving me bananas. Searched on GitHub for what others are doing and found this repo. On release version of Xcode 15, the issue remains unfortunately :(

(posting for others who are googling, because there's not much out there).

See video:

https://github.com/JuniperPhoton/Widget-Intermediate-Animation/assets/1131967/1e549294-fd1a-471a-9f84-dc2faa97c19f

iandundas commented 11 months ago

I'm very curious what Sindre is doing, he has build basically this same app (apparently in SwiftData)

app

Here he has this behaviour working:

https://github.com/JuniperPhoton/Widget-Intermediate-Animation/assets/1131967/fdc9adf3-cc93-4cf9-80d1-72804c0b7bac

JuniperPhoton commented 11 months ago

There is a workaround posted before(see my previous comment). It's fine by me for now because I won't use SwiftData for production because supporting only iOS 17 will have very few users.

iandundas commented 11 months ago

Indeed that workaround works - thanks for fast reply. It's a shame to abandon @Query - it seems kinda fundamental..!

Unfortunately, fetching Models from a background context means they can't be used in the UI :(

By default the @Query Marco uses MainContext to fetch data.

I don't think this can be changed - that might actually be a workaround if it could. A shame.

And it's ok to update the data in an arbitrary context in Widget extension.

Correct, it doesn't seem to matter which context you use in the Widget. Also saving (or not) doesn't make a difference.

iandundas commented 11 months ago

Correction:

Unfortunately, fetching Models from a background context means they can't be used in the UI :(

Actually, this works well enough. The fetched models can still be used in the UI without issue.

CleanShot 2023-09-20 at 09 50 58@2x

JuniperPhoton commented 11 months ago

Actually you can use modelContext modifier to set the context to be used in @Query. Even if you are creating your own ModelContext there is no need to abandon the @Query Marco if it's off this case.

But the key of this workaround is to create a ModelContext each time you fetch items. If you reuse a ModelContext created before you still can't get the right results.

iandundas commented 11 months ago

Thanks so much, was banging my head on that throughout the betas & was amazed it released with that bug. Now I can progress! 🙌

codefruitio commented 11 months ago

This workaround got my app working as well. Thanks @JuniperPhoton

stephenfung98 commented 7 months ago

Can I get a code sample on how to use this workaround?

I put .modelContext(ModelContext(sharedModelContainer)) on the content view

and the following in onChange(of: scenePhase):

            guard let modelContainer = try? ModelContainer(for: Item.self) else { return }

            let descriptor = FetchDescriptor<Item>()
            let fetchedContributions = try? ModelContext(modelContainer).fetch(descriptor)

            for item in items {
                if fetchedContributions?.first(where: { $0.id == item.id })?.completedDate == nil && item.completedDate != nil {
                    toggleComplete(item: item)
                } else if fetchedContributions?.first(where: { $0.id == item.id })?.completedDate != nil && item.completedDate == nil {
                    toggleComplete(item: item)
                }
            }

but it seems to have multiple ModelContext as the items sometimes do not save.

thanks :)

julianfbeck commented 6 months ago

Thanks for the idea of a seperate context, that works for me when fetching data, but how would i then prevent another cloudkit instance from beeing created if i use cloudkkit? CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](585): <NSCloudKitMirroringDelegate: 0x600003b42ca0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134410 "CloudKit setup failed because there is another instance of this persistent store actively syncing with CloudKit in this process."

ramzesenok commented 4 months ago

I did something nasty but it seems to work fine :) In a nutshell – yes, SwiftData won't pull the changes from Widget when the app enters foreground but it will do so if you try to change the corresponding item in the app. So what I did is following:

@Environment(\.scenePhase) var scenePhase

var body: some View {
    ...
        .onChange(of: scenePhase) { _, newValue in
            if case .active = newValue {
                items.forEach { $0.title = $0.title }
            }
        }
}

Setting the same title won't change a thing but it will force SwiftData to pull the changes and apply them. That being said I've only a bunch of items at any given time, not sure how it's gonna work with hundreds or more but at least it should help those who have only a few at a given screen

bradhowes commented 2 months ago

@ramzesenok your solution worked for me! I'm considering SwiftData to manage app state that is shared with AUv3 app extension. In my demo using 2 apps that share the same app group, I was only seeing additions and deletions, but not property changes. The forEach hack did the trick to update the view. Wow.

codefruitio commented 2 months ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

rizwan95 commented 1 month ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

Can you explain how it can help?

codefruitio commented 1 month ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

Can you explain how it can help?

I have yet to test, but around the 5:18 mark in the video, the speaker gives an example of how SwiftData History can be used to fetch changes from a widget in order to update the app UI.

I can try to do some testing this week

rizwan95 commented 1 month ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

Can you explain how it can help?

I have yet to test, but around the 5:18 mark in the video, the speaker gives an example of how SwiftData History can be used to fetch changes from a widget in order to update the app UI.

I can try to do some testing this week

Okay, I will also try.

codefruitio commented 1 month ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

Can you explain how it can help?

I have yet to test, but around the 5:18 mark in the video, the speaker gives an example of how SwiftData History can be used to fetch changes from a widget in order to update the app UI. I can try to do some testing this week

Okay, I will also try.

@rizwan95 I think I got SwiftData History implemented correctly. I have my app/widget updating in both directions in a sample app. Here is the link to the repo: https://github.com/codefruitdev/SwiftDataWidget

rizwan95 commented 1 month ago

Apple just announced SwiftData History, which can be used to resolve this issue. Here's the video from WWDC: https://www.youtube.com/watch?v=K2FzpebEL_4&list=PL5ZA0WjgLYi1c3otN7tXl5K5H6MOhIR__&index=22

Can you explain how it can help?

I have yet to test, but around the 5:18 mark in the video, the speaker gives an example of how SwiftData History can be used to fetch changes from a widget in order to update the app UI.

I can try to do some testing this week

Okay, I will also try.

@rizwan95 I think I got SwiftData History implemented correctly. I have my app/widget updating in both directions in a sample app. Here is the link to the repo: https://github.com/codefruitdev/SwiftDataWidget

Woah! Thank you very much!

Nikolomoec commented 2 days ago

I did something nasty but it seems to work fine :) In a nutshell – yes, SwiftData won't pull the changes from Widget when the app enters foreground but it will do so if you try to change the corresponding item in the app. So what I did is following:

@Environment(\.scenePhase) var scenePhase

var body: some View {
    ...
        .onChange(of: scenePhase) { _, newValue in
            if case .active = newValue {
                items.forEach { $0.title = $0.title }
            }
        }
}

Setting the same title won't change a thing but it will force SwiftData to pull the changes and apply them. That being said I've only a bunch of items at any given time, not sure how it's gonna work with hundreds or more but at least it should help those who have only a few at a given screen

It worked for me, thanks!!