Reputation: 17486
Since Xcode 12.5, I am seeing a lot of "Unable to present. Please file a bug." console logs in the Xcode console which I remember not seeing prior to 12.5.
This message is shown when I am using NavigationLink
from a parent view to navigate to a child view, and if logic in the child view updates one of the states that parent view depends on.
Below is a sample pseudo code where a list of messages are shown in the parent view, message detail is shown in the child view from a list, and lastly, independent settings child view.
struct MessageListView: View {
...
@StateObject var messageList = MessageList()
var body: some View {
debugPrint("!!MessageListView has been redrawn!!")
return VStack {
NavigationLink(destination:SettingsView()){
Text("Go to settings")
}
ForEach(messageList.data.sorted(by: {$0.key < $1.key}), id:\.key) { k, m in
NavigationLink(destination:MessageView(message:...){
Text(m.text)
}
}
}
}
}
struct MessageView: View {
...
@ObservedObject var messageList : MessageList
@ObservedObject var message : Message
...
var body: some View {
Text(...)
.onAppear {
messageList.readAndIncrement(message.id) //<- This updates both this view & parent view.
}
}
}
class MessageList : ObservableObject {
@Published var data : [String:Message] = [:]
func readAndIncrement(id: String){
//Modify one of the message in dictionary.
}
}
So when user clicks on message and traverses the navigation like below,
MessageListView -> MessageView
As soon as MessageView
appears on screen, it will increment message's "read count" due to at the logic in onAppear
, which will update data in MessageListView
at the same time.
As soon as that happens, it appears that the parent view, MessageListView
which is observing MessageList objects gets updated, the two following things happen.
debugPrint
will print message !!MessageListView has been redrawn!!
(from source code above) on console, which proves that the parent view has been updated while Navigation is currently showing child view.So SwiftUI seems to be throwing "Unable to present" error log when I am updating parent view's observed data while viewing & interacting in the child view, but I am unsure how I can properly fix to get rid of this error.
The reason that I am thinking this is not a bug, but my error is because of the following.
When user traverses into a completely different view, something like
MessageListView -> SettingsView
and if the following two conditions are met,
then the parent view (MessageListView) gets redrawn for its dependent model's every single update and the child view starts stacking itself like below.
At least, in the above case, Unable to present. Please file a bug.
doesn't get shown in the console.
In the iOS 14.5 & Xcode 12.5 patch notes, the only comments related to NavigationLink
is as follows, but that does not seem to be relevant to my case.
The destination of NavigationLink that only differs by local state now resets that state when switching between links as expected. (72117345)
So my question is... how can I properly manage states (or decouple them) in this case that won't cause funkiness in SwiftUI Navigation?
Is it against SwiftUI's paradigm to update parent view's state data while presenting child view on screen or is this simply a NavigationLink
bug in Xcode 12.5?
Upvotes: 6
Views: 2224
Reputation: 17486
I am answering my own question about the workaround that I found. The solution that worked for me is to use Class
instead of Struct
and update properties of the Class
. This prevented the View
, which is watching the @Published
array/list, from getting updated.
Previously, I was using Struct
for the Message
type. What ended up happening is that any change to one of the properties of the struct/Message will trigger UI updates (which is expected), even if View
is not directly watching the struct itself, but watching the list of structs (MessageList
class's data
property in my case.).
I converted Message
from Struct
to Class
that implements ObservableObject
and updates its properties. That prevented parent View, which dependent on the list of Messages from getting redrawn, when one of the list elements had to be updated.
In summary, when you assign @Published
annotation to an array of Struct
, list item addition/removal as well as any change to one of the properties of the struct item in the list will trigger dependent View updates in SwiftUI.
On the other hand, if you assign @Published
annotation to an array of Class
, only list item addition/removal will trigger SwiftUI update. Any change to one of the properties of the Class won't trigger dependent View updates in SwiftUI.
Upvotes: 2