Turning Swift compile-time safety into safety for your users

Sometimes good coding practices lead to good UX

Oleg Dreyman
Jan 16, 2018 · 4 min read

What happened?

Technically, today nothing stops you as a developer from unintentionally or accidentally making irreversible changes (e.g. deleting user data) without confirmation from the user. Of course, we try to mitigate this risk as much as possible, writing UIAlertController code all over the place (or even making cute convenience closure-based functions for this purpose), but the APIs we write do nothing to prevent this from happening.

final class Images {

private var images: [UIImage]

func image(at index: Int) -> UIImage {
return images[index]
}

func add(_ image: UIImage) {
images.append(image)
}

func delete(imageAt index: Int) {
images.remove(at: index)
}

}
@objc func didPressDeleteButton(sender: UIButton) {
images.delete(imageAt: currentImageIndex)
}

What’s the conventional solution for this?

Most of the time the developer would try to approach this problem at the view layer — aka creating and displaying UIAlertController right there, at didPressDeleteButton. This, of course, solves the problem most of the time, but it has its obvious disadvantages:

  • Despite that, the developer still can accidentally delete the image not at the view layer, but somewhere in the business logic — without thinking twice about it.

So what’s the “right” solution?

What we really want is our Images class simply not allowing to perform a delete without user confirmation. To help us achieve that functionality, let’s create a UserConfirmationRequired struct:

struct UserConfirmationRequired {

private let performDestructiveAction: () -> ()

init(destructiveAction: @escaping () -> ()) {
self.performDestructiveAction = destructiveAction
}

func performWithUserConfirmation(alertTitle: String, alertMessage: String, alertDestructiveActionTitle: String, completion: @escaping (Bool) -> ()) {

// retrieving view controller to show alert from
guard let window = UIApplication.shared.delegate?.window else {
print("No window")
completion(false)
return
}
guard let viewController = window?.rootViewController else {
print("No view controller")
completion(false)
return
}

// creating and showing an alert
let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .actionSheet)

let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in completion(false) })

let destructive = UIAlertAction(title: alertDestructiveActionTitle, style: .destructive, handler: { _ in
self.performDestructiveAction()
completion(true)
})

alert.addAction(cancel)
alert.addAction(destructive)
viewController.present(alert, animated: true)
}

}
func deleteAction(ofImageAt index: Int) -> UserConfirmationRequired {
return UserConfirmationRequired(destructiveAction: {
self.images.remove(at: index)
})
}
@objc func didPressDeleteButton(sender: UIButton) {
let title = "Delete an image"
let message = "This action can't be undone. Are you sure?"
let delete = "Delete"
images.deleteAction(ofImageAt: currentImageIndex).performWithUserConfirmation(alertTitle: title, alertMessage: message, alertDestructiveActionTitle: delete) { (deleted) in
print("Deleted:", deleted)
}
}


AnySuggestion

A Swift & Cocoa blog by Oleg Dreyman

Oleg Dreyman

Written by

Swift enthusiast. https://github.com/dreymonde

AnySuggestion

A Swift & Cocoa blog by Oleg Dreyman

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade