Challenge: Encapsulate Sheet Presentation

Maybe its just me, but I just couldn’t get my head around this challenge. I think the problem I’m having is the whole concept of closures. I went back over Chapter 15 and did some other reading to try and understand what is going on here. Swift syntax can be so terse that to the inexperienced it is hard to work out exactly what is going on. After a week of thinking about it and coming back to it after a good nights sleep (not quite 10 hours as Aaron suggests but more than I usually get) I had still not fully grasped the concept. Today I tried again and it worked. I’m still not entirely sure why it worked but it did. In the end, as usual, it was really simple.

Did anyone else struggle with this challenge or is it just me that is not understanding closures?

In case anyone is looking for some code. Here is the relevant parts of the ConfigurationWindowController:

[code]enum ModalResult : NSModalResponse {
case Accept
case Cancel
}

// …

@IBAction func okayButtonClicked(sender: NSButton) {

    window?.endEditingFor(nil)
    dismissWithModalResponse(.Accept)
}

@IBAction func cancelButtonClicked(sender: NSButton) {
    dismissWithModalResponse(.Cancel)
}

private func dismissWithModalResponse(response: ModalResult) {
    // This is a bad idea if someone uses beginSheet(...) instead of presentAsSheetOnWindow(...) as they 
    // will most likely compare the result to NSModalResponseOK and so on.
    window!.sheetParent!.endSheet(window!, returnCode: response.rawValue)
}

func presentAsSheetOnWindow(window: NSWindow, completionHandler: (DieConfiguration?)->() = {_ in } ) {
    
    window.beginSheet(self.window!) { rawResponse in
        
        guard let response = ModalResult(rawValue: rawResponse) else {
            fatalError("Failed to map \(rawResponse) to ModalResult")
        }
        
        switch response {
            case .Accept: completionHandler(self.configuration)
            case .Cancel: completionHandler(nil)
        }
    }
    
}[/code]

One should note though, that creating a ModalResult enum like shown in the book is not necessarily the best idea.
The problem is that the window controller cannot prevent a caller from using the AppKit method beginSheet(…) to display this window controller (instead of our carefully crafted presentAsSheetOnWindow(…).
This means that the dismissWithModalResponse(…) would in that case return some Integer as response that might have nothing to do with what AppKit expects (NSModalResponseOK for example)
So when creating some fancy replacement for a method AppKit provides it might be wise to maintain compatibility (in that case either create separate methods to end the sheet for both code paths or stick with NSModalResponseOK or NSModalResponseCancel)

Should Swift ever allow enum cases be initialized with constants and not only literals, one might circumvent the problem by declaring the enum like this:

// Will not compile! enum ModalResult : NSModalResponse { case Accept = NSModalResponseOK case Cancel = NSModalResponseCancel }but that’s not possible (yet?)

1 Like

[quote=“tkrajacic”]In case anyone is looking for some code. Here is the relevant parts of the ConfigurationWindowController:

[code]enum ModalResult : NSModalResponse {
case Accept
case Cancel
}

// …

@IBAction func okayButtonClicked(sender: NSButton) {

    window?.endEditingFor(nil)
    dismissWithModalResponse(.Accept)
}

@IBAction func cancelButtonClicked(sender: NSButton) {
    dismissWithModalResponse(.Cancel)
}

private func dismissWithModalResponse(response: ModalResult) {
    // This is a bad idea if someone uses beginSheet(...) instead of presentAsSheetOnWindow(...) as they 
    // will most likely compare the result to NSModalResponseOK and so on.
    window!.sheetParent!.endSheet(window!, returnCode: response.rawValue)
}

func presentAsSheetOnWindow(window: NSWindow, completionHandler: (DieConfiguration?)->() = {_ in } ) {
    
    window.beginSheet(self.window!) { rawResponse in
        
        guard let response = ModalResult(rawValue: rawResponse) else {
            fatalError("Failed to map \(rawResponse) to ModalResult")
        }
        
        switch response {
            case .Accept: completionHandler(self.configuration)
            case .Cancel: completionHandler(nil)
        }
    }
    
}[/code]

One should note though, that creating a ModalResult enum like shown in the book is not necessarily the best idea.
The problem is that the window controller cannot prevent a caller from using the AppKit method beginSheet(…) to display this window controller (instead of our carefully crafted presentAsSheetOnWindow(…).
This means that the dismissWithModalResponse(…) would in that case return some Integer as response that might have nothing to do with what AppKit expects (NSModalResponseOK for example)
So when creating some fancy replacement for a method AppKit provides it might be wise to maintain compatibility (in that case either create separate methods to end the sheet for both code paths or stick with NSModalResponseOK or NSModalResponseCancel)

Should Swift ever allow enum cases be initialized with constants and not only literals, one might circumvent the problem by declaring the enum like this:

// Will not compile! enum ModalResult : NSModalResponse { case Accept = NSModalResponseOK case Cancel = NSModalResponseCancel }but that’s not possible (yet?)[/quote]

The ModalResult type is for your clients, not yourself! You still have to use NSModalResponseOK/Cancel inside your beginSheet implementation. Inside said implementation, you translate the system’s response to your own ModalResult code for whoever is calling your presentAsSheetOnWindow. However, you already are supposed to be using non-NIL/NIL for the die-configuration object as your success/fail condition for the sheet, so you don’t need the ModalResult type here at all.