I have a document based iOS app, and when it first opens a document, the main view controller calls UIDocument.open
.
document.open { success in
if success { ... set up UI ... }
else { ??? }
}
The problem here is that if success
is false, I don't have access to the error. Often, Apple's APIs will pass an optional Error
parameter to the callback in these situations, but for some reason they don't here.
I found this method that I can override in my application's subclass of UIDocument
:
override func handleError(_ error: Error, userInteractionPermitted: Bool) {
Now in that method I have the Error
but I don't have easy access to the view controller that called document.open
, which I need to present something like a UIAlertController
to display the error message. This handleError
method is also called on a non-main thread.
It looks like I need to coordinate by passing information in instance or global variables. Because that seems more awkward than the Apple's usual design -- where I expect the Error to be available in the open
's completion handler, I thought I might be missing something.
Is there a another recommended way to get the error object and present a message to the user?
Rob,
If you really want to be "swifty", you could implement a closure to do exactly this without the need for static / global variables.
I would start by defining an enum that models the success and failure cases of an API call to UIDocument. The generic Result enum is a pretty common way of doing this.
enum Result<T> {
case failure(Error)
case success(T)
}
From there I would define an optional closure in your class that handles the outcome of UIDocument.open
The implementation I would do is something like so:
class DocumentManager: UIDocument {
var onAttemptedDocumentOpen: ((Result<Bool>) -> Void)?
func open(document: UIDocument){
document.open { result in
guard result else { return } // We only continue if the result is successful
// Check to make sure someone has set a function that will handle the outcome
if let onAttemptedDocumentOpen = self.onAttemptedDocumentOpen {
onAttemptedDocumentOpen(.success(result))
}
}
}
override func handleError(_ error: Error, userInteractionPermitted: Bool) {
// Check to make sure someone has set a function that will handle the outcome
if let onAttemptedDocumentOpen = self.onAttemptedDocumentOpen {
onAttemptedDocumentOpen(.failure(error))
}
}
}
Then I from whatever class will be using the DocumentManager you would do something like this:
class SomeOtherClassThatUsesDocumentManager {
let documentManger = DocumentManager()
let someViewController = UIViewController()
func someFunction(){
documentManger.onAttemptedDocumentOpen = { (result) in
switch result {
case .failure(let error):
DispatchQueue.main.async {
showAlert(target: self.someViewController, title: error.localizedDescription)
}
case .success(_):
// Do something
return
}
}
}
}
Bonus: This is a static function I wrote to display a UIAlertController on some view controller
/** Easily Create, Customize, and Present an UIAlertController on a UIViewController
- Parameters:
- target: The instance of a UIViewController that you would like to present tye UIAlertController upon.
- title: The `title` for the UIAlertController.
- message: Optional `message` field for the UIAlertController. nil by default
- style: The `preferredStyle` for the UIAlertController. UIAlertControllerStyle.alert by default
- actionList: A list of `UIAlertAction`. If no action is added, `[UIAlertAction(title: "OK", style: .default, handler: nil)]` will be added.
*/
func showAlert(target: UIViewController, title: String, message: String? = nil, style: UIAlertControllerStyle = .alert, actionList: [UIAlertAction] = [UIAlertAction(title: "OK", style: .default, handler: nil)] ) {
let alert = UIAlertController(title: title, message: message, preferredStyle: style)
for action in actionList {
alert.addAction(action)
}
// Check to see if the target viewController current is currently presenting a ViewController
if target.presentedViewController == nil {
target.present(alert, animated: true, completion: nil)
}
}