nalexn / clean-architecture-swiftui

SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Xcode 12 GM warnings when updating local state from AppState

NeverwinterMoon opened this issue · comments

Not sure how critical this is, I just jumped into Xcode 12 GM today, but I have loads of warnings which were not there with Xcode 11. Once again, not sure it actually affects anything, but maybe worth mentioning.

Basically, all the local state updates which come from the injected AppState updates are marked like this:

image

Maybe it makes sense to use the new onChange(of:perform:) API?

These warnings cannot be ignored, we should find the cause.

I can assume you have some code in your project that updates the AppState while the view is being rendered: an example.

I double-checked the sample project with the new Xcode and it doesn't throw these warnings, and using onReceive for updating the local view state should be perfectly legit.

You can try to put a breakpoint in the AppState update to find the call trace originating from SwiftUI render cycle

My only culprit for something like you mentioned would be the use of willSet/didSet, but I don't even have those around the view I get the warnings at. But I did manage to find a connection between those warnings and the navigation. When I navigate to the view and do anything around it, there are no warnings. When I navigate one step deeper down the navigation stack and then return to the view in question, I immediately get those warnings.

Oh, and I can't reproduce this with iOS 13 with the same code. Only 14.x

OK, I managed to reproduce the problem with minimal changes in your project: NeverwinterMoon@a98fd5f

The actual problem manifests in broken navigation (automatic navigation back), but this is also when I get the warnings mentioned above...

Steps:

  • Load the app

  • Tap on "Afghanistan"

  • Tap on the flag cell (anywhere around the flag image)

  • Tap "Go to BLUE"

  • Tap "Back"

  • Tap "Back" ("< Afghanistan")

  • Tap "Back" ("< Countries")

We are on the initial page again

  • Tap "Afghanistan"
  • Tap on the flag cell (anywhere around the flag image)
  • Observe (opens the new view and then automatically navigates back)

The above is 100% reproducible with iOS 14 and not reproducible with iOS 13.

ezgif-7-3864e0a2983a

It looks like the issue is at Binding#onSet. The same type of navigation works just fine using @State variables.

This routing approach was done to handle deep linking comfortable, but maybe now with iOS 14's onOpenURL that can be attached to any view it might make sense to use a different one?

OK, I did tangle everything together. So I'll go step by step.

First of all, I have this navigation: View1 -> View2 -> View3 -> View4

The original problem

The warnings - they are actually caused by having this on View2:

.onAppear {
   $someStateVar.wrappedValue = someValueFromUserDefaults
}

I also tried to wrap the above in DispatchQueue.main.async and it didn't help.

Interestingly enough, when I go: View1 -> View2, there are no warnings.
When I got View1 -> View2 -> View3 -> back to View2, the warnings pop up.
Most importantly, this is only with iOS 14, no warnings on iOS 13 with exactly the same code.

The navigation problem

This one is a bummer. I managed to reproduce it in your project as well, as I wrote above. It's specific only to iOS 14 and works just fine with iOS 13. Basically, if the navigation is deeper than 3 views, the routing approach (with the routingBinding) breaks.

I am fine with using the new to iOS 14 onOpenURL, as it allows to attach URL handling logic granularly to every view, which is great, but I still need to support iOS 13 somehow... So I am a bit stuck.

Yeah, I was hoping programmatic navigation will become less buggy with the iOS update, but it looks like things got only worse.

I'll experiment more with your commit and try to find a workaround. I can see one way to unblock you if you're actively working on some project, but this involves quite some refactoring.

I recently wrote an article, where I concluded that for now, I wouldn't trust SwiftUI with programmatic routing and suggested to use UIKit for navigation, while keeping all the screens written in pure SwiftUI. Consider taking this approach. There is also a project for reference.

Looks like a nice approach, that https://github.com/nalexn/uikit-swiftui. I wish I'd used that to begin with. I'll try it out at some point and see how it fits the project.

For now, I went with something like @StateObject private var routing = GlobalRouting() just for iOS 14. That was an easy change to make...