Widget-Intermediate-Animation icon indicating copy to clipboard operation
Widget-Intermediate-Animation copied to clipboard

Widget changes not syncing to main app

Open codefruitio opened this issue 2 years ago • 30 comments

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?

codefruitio avatar Aug 30 '23 15:08 codefruitio

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.

JuniperPhoton avatar Aug 31 '23 02:08 JuniperPhoton

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

codefruitio avatar Aug 31 '23 04:08 codefruitio

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?

JuniperPhoton avatar Aug 31 '23 11:08 JuniperPhoton

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: @.***>

codefruitio avatar Aug 31 '23 12:08 codefruitio

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.

JuniperPhoton avatar Aug 31 '23 14:08 JuniperPhoton

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

codefruitio avatar Aug 31 '23 18:08 codefruitio

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?

JuniperPhoton avatar Sep 01 '23 01:09 JuniperPhoton

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 avatar Sep 01 '23 13:09 codefruitio

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

codefruitio avatar Sep 03 '23 02:09 codefruitio

Thanks. I will also report this issue later.

JuniperPhoton avatar Sep 03 '23 11:09 JuniperPhoton

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.

JuniperPhoton avatar Sep 10 '23 06:09 JuniperPhoton

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 avatar Sep 20 '23 05:09 iandundas

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

iandundas avatar Sep 20 '23 05:09 iandundas

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.

JuniperPhoton avatar Sep 20 '23 06:09 JuniperPhoton

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 avatar Sep 20 '23 07:09 iandundas

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

iandundas avatar Sep 20 '23 07:09 iandundas

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.

JuniperPhoton avatar Sep 20 '23 08:09 JuniperPhoton

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

iandundas avatar Sep 20 '23 08:09 iandundas

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

codefruitio avatar Sep 20 '23 18:09 codefruitio

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

stephenfung98 avatar Jan 10 '24 06:01 stephenfung98

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."

julianfbeck avatar Jan 28 '24 10:01 julianfbeck

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

ramzesenok avatar Apr 12 '24 09:04 ramzesenok

@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.

bradhowes avatar May 26 '24 21:05 bradhowes

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

codefruitio avatar Jun 14 '24 03:06 codefruitio

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?

rizwan95 avatar Jul 04 '24 11:07 rizwan95

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

codefruitio avatar Jul 04 '24 11:07 codefruitio

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 avatar Jul 04 '24 11:07 rizwan95

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

codefruitio avatar Jul 09 '24 02:07 codefruitio

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!

rizwan95 avatar Jul 09 '24 03:07 rizwan95

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!!

Nikolomoec avatar Aug 20 '24 21:08 Nikolomoec