pointfreeco / swiftui-navigation

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using IfLet requires cancellation/confirmation twice

appfrosch opened this issue · comments

Describe the bug
I am driving a detail screen with an Edit/Confirm logic. Entering the "edit" works as expected, but the Confirm- and the Cancel-button need to be tapped twice in order to end editing. The editing itself is bound to an optional string that is set when the Edit-button is tapped.

The view is driven off of an IfLet.

Please forgive me if this is something explained in one of your videos–as a subscriber of your content feel free to just point me to a related video if available :-)

To Reproduce
This is a sample project mimicking the behaviour I see in my project.

import SwiftUI
import SwiftUINavigation

@main
struct so_IfLetIssueApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State private var title = "Some fancy title"
    @State private var editingTitle: String?
    
    var body: some View {
        NavigationStack {
            Form {
                IfLet($editingTitle) { $editingTitle in
                    TextField("Title", text: $editingTitle)
                } else: {
                    Text(title)
                }
            }
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button(editingTitle == nil ? "Edit" : "Confirm") {
                        if let editingTitle {
                            title = editingTitle
                            self.editingTitle = nil
                        } else {
                            editingTitle = title
                        }
                    }
                }
                if editingTitle != nil {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Cancel") {
                            editingTitle = nil
                        }
                    }
                }
                ToolbarItem(placement: .bottomBar) {
                    Text("editingTitle is \"\(editingTitle == nil ? "nil" : editingTitle!)\"")
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
    
}

Expected behavior
Tapping the "Confirm" or the "Cancel" button once should end editing by setting the optional binding to nil.

Screencast

Simulator.Screen.Recording.-.iPhone.SE.3rd.generation.-.2022-11-17.at.08.42.01.mp4

Environment

  • swiftui-navigation version [e.g. 0.1.0]
  • Xcode Version 14.1 (14B47b)
  • Swift 5.5
  • OS: macOS 12.6.1 (21G217)

Hi @appfrosch, thanks for the nice repro of this problem!

Unfortunately, this is a long-standing, known SwiftUI bug :/

The problem is, for whatever reason, TextFields always write twice to their bindings. We filed a bug for it (gist), so if you have the time we recommend you duplicate.

Luckily there is a word around. You just need to transform the binding you hand to TextField so that it removes duplicate writes. And this library even comes with that tool:

TextField("Title", text: $editingTitle.removeDuplicates())

Can you confirm that works for you?

Hi @appfrosch, glad it worked!

We also have an update to swiftui-navigation coming out next week that will fix this problem in a way that does not require you to litter removeDuplicates on all your text field bindings. So be sure to update when we release. 😁

This is fixed by default in 0.4.x 😄