BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

SOLVED: Update forces view to be dismissed (bug or error?) - UltimatePortfolio: Editing projects

Forums > Articles

@cgaaf  

I just completed the "Editing projects" article from the UltimatePortfolio series and I'm noticing a bug when I attempt to make any chnages in the EditProjectView. Each time I attempt to make any state change the view is immediately dismissed and I jump back to the ProjectsView. Example

This seems to specifically happen when the update() function is called. My update() function is identical to Paul's.

func update() {
        project.title = title
        project.detail = detail
        project.color = color
    }

So far I think it's related to the fact that the project property in the parent view (ProjectHeaderView) uses the @ObservedObject property wrapper. It seems that when the project property in the parent ProjectHeaderView updates, the EditProjectView within the NavigationLink gets dismissed. Is anyone else having this issue? It doesn't seem to happen in Paul's video. I've gone through and I don't see any obvious discrepencies between my code and Paul's.

What am I missing?

5      

@cgaaf  

So it looks like this was specifically a bug with the latest Xcode 12.2 Beta. It's working correctly on the Xcode 12.1 release

7      

@twostraws  Site AdminHWS+

Oh lovely. Please file some feedback with Apple – this might actually ship into production, and it's quite a breaking change!

6      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

I just sent an email to Paul about this. I thought it was an issue with the code from the latest lesson (adding/deleting).

It only occurs when there's two or more projects and works as intended with only one project.

5      

Thank you for the 'heads up'. I was going around in circles trying to get this to work (beta version), rolled back to 12.1 release and all works as it should!

5      

Was also stumped by this for a while in 12.2 beta 3, glad I came across this post! Thank you, will also file some feedback!

5      

This was driving me nuts as well and I was thinking I'd introduced a bug somewhere random! Thanks for finding the issue.

Xcode 12.2 beta 4 (RC) seems to have resolved the issue - good job, otherwise we'd be pushing back on that version :-)

5      

Well, I'm late to the party, but I got the same bug in Xcode 12.2 (non-beta)

Did anyone figure out something that really solves that bug/problem?

5      

It seems to be fixed in Xcode 12.3 beta (12C5020f)

5      

Not working here on latest beta.

5      

Same bug here with Xcode 12.2 on Catalina 10.15.7. Editing items is fine but making any edit to a Project causes the instance of EditProjectView to be dismissed as soon as the handler is called.

5      

This bug still exists in Xcode 12.3

5      

Bug is still an issue with Xcode 12.3(12C33)

6      

I am still able to recreate the bug consistently with the latest Xcode, as well. I'm working around the bug with the following line. It solves the issue enough for me, but negates the use of the onChange() extension.

TextField("Project name", text: $title, onEditingChanged: { if !$0 { update() } })

The following also works, but requires return to be pressed.

TextField("Project name", text: $title, onCommit: update)

6      

Has anyone a fix for this? It's still there with Xcode 12.4.

5      

It appears to be fixed once again in 12.5 beta (12E5220o).

5      

great

5      

I don't think this is solved, or at least not in all situations. I believe I'm hitting this same circumstance.

XCode 13.2.1 MacOS 12.2.1 iPhone 13 simulator iOS 15.something

I'm genning up an object in the root view and passing it into the environment using @environmentObject(...), navigating via a NavigationView in the root view, updating things along the way (in this case the object is a data collector for account signup information across several screens). I get a few screens in, and whammo, any udpates to the @EnvironmentObject private var signupData: SignupData object causes a dismiss. It's not consistent (meaning which screen will do it), but whatever screen it happens on, happens 100% of the time.

I don't think there's anything overly fancy about my data collector object, and even on a stripped-to-the-bone test View, it still happens (pasted below)

The data collector object

import Foundation

enum AccountType: Int {
    case none
    case journalOnly
    case singleIncident
    case preventive
}

class SignupInfo : ObservableObject {
    // seeding sample data for less typing during dev & testing
    @Published var email: String = "bob@bobberson.com"
    @Published var password: String = "skatanna99!"
    @Published var password2: String = "skatanna99!"

    @Published var firstname: String = "bob"
    @Published var lastname: String = "bobberson"

    @Published var address1: String = "123 Some Town Road"
    @Published var address2: String = ""
    @Published var city: String = "Newark"
    @Published var region: String = "DE"
    @Published var postalCode: String = "19701"

    @Published var phone: String = ""

    @Published var ccnumber: String = ""
    @Published var ccname: String = ""

    @Published var accountType: AccountType = .journalOnly
}

(not real info of course)

The view that's crashing out or dismissing on data change. I wondered if it was a threading thing, so I have with and without a dispatch. No change to the behavior. I think it's related to the depth into the navigationLink stack.

import SwiftUI

struct SignupServicesView: View {

    //@StateObject private var viewModel:ViewModel = ViewModel()
    @EnvironmentObject private var signupInfo: SignupInfo
    //@EnvironmentObject private var opData: OpData

    var body: some View {
        Text("account type is set to \(signupInfo.accountType.rawValue)")
            .onTapGesture {
                signupInfo.accountType = .preventive
            }
            .onLongPressGesture {
                DispatchQueue.main.async {
                    signupInfo.accountType = .preventive
                }
            }
    }

}

frustrating!

4      

And moving from @ObservedObject (of some far-off object) or @EnvironmentObject to just parking the data in my viewModel works as you would expect. Just posting this for the sake of completeness.

import SwiftUI

struct SignupServicesView: View {

    @StateObject private var viewModel:ViewModel = ViewModel()
    //@EnvironmentObject private var opData: OpData

    var body: some View {
        Text("account type is set to \(viewModel.accountType.rawValue)")
            .onTapGesture {
                viewModel.accountType = .preventive
            }
            .onLongPressGesture {
                DispatchQueue.main.async {
                    viewModel.accountType = .preventive
                }
            }
            .navigationBarHidden(true)
    }

}

and the view model object...

import Foundation

extension SignupServicesView {
    class ViewModel : ObservableObject {
        @Published var accountType: AccountType = .journalOnly
    }
}

4      

It's not the enum data type. I removed it. Even doing basic stuff like changing a string is a problem. Here's my ongoing testing scenario...

Interestingly, when I move things into the viewModel, it all works normal, as expected. No surprise. But I still want a common place to drop the data, without creating CoreData or UserDefaults entries.

When I move the underlying data collector object into a global context and attach it to the view model, it works if I hop the properties through the view model (view updates view model, view model updates global object).

But if I just expose the underlying data connector directly it still dismisses the View in this crazy weird way.

Hang the global object off the app's main view (for testing only)

public static var signupInfo = SignupInfo()

Then in the viewModel...

import Foundation

extension SignupServicesView {
    class ViewModel : ObservableObject {
        // works great
        @Published var firstname = theApp.signupInfo.firstname
    }
}

And the View:

import SwiftUI

struct SignupServicesView: View {

    @StateObject private var viewModel:ViewModel = ViewModel()

    var body: some View {
        Text("account type is set to \(viewModel.firstname)")
            .onTapGesture {
                viewModel.firstname = "joe"
            }
            .onLongPressGesture {
                DispatchQueue.main.async {
                    viewModel.firstname = "joe"
                }
            }
            .navigationBarHidden(true)
    }
}

That all works fantastic.

Exposing the data directly doesn't work. The viewModel:

import Foundation

extension SignupServicesView {
    class ViewModel : ObservableObject {
        @Published var signupInfo = theApp.signupInfo
    }
}

The View:

import SwiftUI

struct SignupServicesView: View {

    @StateObject private var viewModel:ViewModel = ViewModel()

    var body: some View {
        Text("account type is set to \(viewModel.signupInfo.firstname)")
            .onTapGesture {
                viewModel.signupInfo.firstname = "joe"
            }
            .onLongPressGesture {
                DispatchQueue.main.async {
                    viewModel.signupInfo.firstname = "joe"
                }
            }
            .navigationBarHidden(true)
    }
}

So, it really feels like talking to my SignupInfo object directly is a problem for (some of) my Views when they are deep-ish into the Navigation stack, no matter how they become aware of it. But I'll be a pickled monkey if I can figure out why.

4      

Solved redux: for reasons that make no sense to me on initial blush, because I'm not updating any of the values used on the previous screen, it seems it was forcing a redraw on the previous screen, which then, again for reasons that simply don't make much sense, pops the current view in favor of foregrounding and redrawing the previous screen.

I fully understand the HOW of it, but the WHY of it is... ahem... pardon my french... pretty f$#cking stupid. I don't often scratch my head at the way Apple does things, but when I do, it's because they've gone to new levels of "what the heck were thinking, boy?"

(sigh)

Anyway, I just hid the data collector object behind ViewModels for each screen, and it's exposed at the root View's level as a static global. Cheaty. Hackey. Effective. Done.

4      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.