salo.dm
salo.dm

Reputation: 2315

SwiftUI EditButton action on Done

How do you set an action to perform when the user hits the EditButton when it appears as "Done"? Is it even possible to do so?

Note that this is not the same as performing an action at each of the individual edits the user might do (like onDelete or onMove). How do you set an action to perform when the user is finished making all changes and ready to commit them?

It's necessary because I'm making all changes on a temporary copy of the model and will not commit the changes to the actual model until the user hits "Done". I am also providing a "Cancel" Button to discard the changes.

struct MyView: View {
    @Environment(\.editMode) var mode

    var body: some View {
        VStack {
            HStack {
                if self.mode?.value == .active {
                    Button(action: {
                        // discard changes here
                        self.mode?.value = .inactive
                    }) {
                        Text("Cancel")
                    }
                }

                Spacer()

                EditButton()
                // how to commit changes??
            }
            // controls to change the model
        }
    }
}

Is it even possible to set an action for "Done" on the EditButton or would I have to provide my own button to act like an EditButton? I could certainly do that, but it seems like it should be easy to do with the EditButton.

Upvotes: 4

Views: 8291

Answers (4)

Jason Armstrong
Jason Armstrong

Reputation: 1260

New answer for XCode 12/iOS 14 using the onChange modifier.

This approach uses the SwiftUI editMode key path available via @Environment along with the new onChange modifier to determine when the view enters and exits editing mode.

In your view do the following:

@Environment(\.editMode) private var editMode

...

.onChange(of: editMode!.wrappedValue, perform: { value in 
  if value.isEditing {
     // Entering edit mode (e.g. 'Edit' tapped)
  } else {
     // Leaving edit mode (e.g. 'Done' tapped)
  }
})

Upvotes: 13

MScottWaller
MScottWaller

Reputation: 3573

From what I understand, the EditButton is meant to put the entire environment in edit mode. That means going into a mode where, for example, you rearrange or delete items in a list. Or where text fields become editable. Those sorts of things. "Done" means "I'm done being in edit mode", not "I want to apply my changes." You would want a different "Apply changes," "Save," "Confirm" or whatever button to do those things.

Upvotes: 2

user7014451
user7014451

Reputation:

Your last comment contains a subtle change to things. Until then it sounded like you wanted both a "Done" and "Cancel" button - something very few iOS apps do.

But if you want a "Done" button with a "double-checking things, are you sure" you should be able to code such a thing by (possibly) manually going into edit mode and detecting when it's no longer in it. Save a "before" state, add a popup to when it is about to be changed, and you might give the user a rollback option.

FYI - I'm very old school. Not just PC, but mainframe. Not just Microsoft, but SAP. I certainly understand wanting to give the user one last chance before a destructive change - but not too many iOS apps do that. :-)

Upvotes: 0

szemian
szemian

Reputation: 2801

You can use the onDisappear() modifier to perform the action, on a view that you show only on edit mode. There is an example on how to do it in the tutorial of SwiftUI "Working with UI Controls":

if self.mode?.wrappedValue == .inactive {
    ProfileSummary(profile: profile)
} else {
    ProfileEditor(profile: $draftProfile)
        .onDisappear {
            self.draftProfile = self.profile
        }
}

In your sample code above, since you do not have a view shown in edit mode, you can add state to check if you have clicked on "Cancel" or "Done" as below:

struct MyView: View {    
    @Environment(\.editMode) var mode
    @State var isCancelled = false

    var body: some View {
        VStack {
            HStack {
                if self.mode?.wrappedValue == .active {
                    Button(action: {
                            // discard changes here
                            self.mode?.wrappedValue = .inactive
                            self.isCancelled = true
                        }) {
                            Text("Cancel")
                        }
                        .onAppear() {
                            self.isCancelled = false
                        }
                        .onDisappear() {
                            if (self.isCancelled) {
                                print("Cancel")
                            } else {
                                print("Done")
                            }

                        }
                    }

                    Spacer()

                    EditButton()

                    // how to commit changes??
                }
                // controls to change the model
            }
        }
    }

Upvotes: 3

Related Questions