In my application, a NSDocument
subclass mission-critical hardware – users really don’t want to close a document by accident! So, I’ve implemented canClos
Here is a Swift solution to this issue that I received from Apple Developer Technical Support:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
super.canCloseDocumentWithDelegate(self, shouldCloseSelector: "document:shouldClose:contextInfo:", contextInfo: contextInfo)
}
func document(doc:NSDocument, shouldClose:Bool, contextInfo:UnsafeMutablePointer<Void>) {
if shouldClose {
// <Your clean-up code>
doc.close()
}
}
You can solve this with some low level runtime functions:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
let allowed = true // ...or false. Add your logic here.
let Class: AnyClass = object_getClass(delegate)
let method = class_getMethodImplementation(Class, shouldCloseSelector)
typealias signature = @convention(c) (AnyObject, Selector, AnyObject, Bool, UnsafeMutablePointer<Void>) -> Void
let function = unsafeBitCast(method, signature.self)
function(delegate, shouldCloseSelector, self, allowed, contextInfo)
}
If you need to move this behaviour to another method (eg. after a sheet gets confirmation from the user), simply store the delegate and shouldCloseSelector in properties so you can access them later.
So, my current solution to this, is to keep using Objective-C to perform the NSInvocation. The NSDocument
subclass is written in Swift, and calls an Objective-C category to do this bit of work.
Since NSInvocation
does not exist in Swift, I really don’t see any other way.
- (void)respondToCanClose:(BOOL)shouldClose delegate:(id)delegate selector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
NSDocument *doc = self;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:shouldCloseSelector]];
invocation.target = delegate;
invocation.selector = shouldCloseSelector;
[invocation setArgument:&doc atIndex:2]; // Note index starts from 2 - 0 & 1 are self & selector
[invocation setArgument:&shouldClose atIndex:3];
[invocation setArgument:&contextInfo atIndex:4];
[invocation invoke];
}
You can see my sample project: https://github.com/DouglasHeriot/canCloseDocumentWithDelegate
Another option is to use Objective-C to wrap around objc_msgSend
, which is also unavailable in Swift. http://www.cocoabuilder.com/archive/cocoa/87293-how-does-canclosedocumentwithdelegate-work.html#87295
At least as of Swift 4.1, you can do something like:
// Application Logic
myDocument.canClose(
withDelegate: self,
shouldClose: #selector(MyClass.document(_:_:_:)),
contextInfo: nil)
...
// Handler
@objc
private func document(_ doc: NSDocument, _ shouldClose: Bool, _ contextInfo: UnsafeMutableRawPointer) {
...
}