How to use NSMaxXEdge in Cocoa app with Swift (Xcode 6)? - xcode

I've tried to create a Cocoa app which uses NSPopover internally. One of the popover's method is showRelativeToRect: ofView: preferredEdge:, which triggers an popover in Cocoa app.
If you use Objective-C in Xcode 5.1, you can execute the method with the method above, like this:
[popover showRelativeToRect: sender.bounds ofView: sender preferredEdge: NSMaxXEdge];
However, when I tried to use the method in Swift in Xcode 6, the message Use of unresolved identifier 'NSMaxXEdge' was shown when I wrote the following method, which is just a rewrite of the Objective-C method above:
popover.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: NSMaxXEdge
So where is the NSMaxXEdge gone? The official documentation says it is of type NSRectEdge, but what is the NSRectEdge? The doc doesn't link to NSRectEdge page.
Also, Xcode 5 documentation also says it is NSRectEdge, but again, no link exists there. So how can I know about what it is all about?
And finally, if NSMaxXEdge is no longer available in Xcode 6, what is the alternative?

In Foundation/NSGeometary.h of 10.10 SDK:
typedef enum {
NSMinXEdge = 0,
NSMinYEdge = 1,
NSMaxXEdge = 2,
NSMaxYEdge = 3
} NSRectEdge;
So it should be NSRectEdge.from(2) or NSRectEdge(2) or just pass 2 will be fine.
If you try this:
println("NSRectEdge.max: \(NSRectEdge.max)") // 9223372036854775807
println("NSRectEdge.from(2): \(NSRectEdge.from(2))") // 2
println("NSRectEdge(2): \(NSRectEdge(2))") //2
You will know that .max was actually the max positive signed Int for 64-bit.
(Tho, I can't be sure since it's not really mentioned in documentation anywhere.)

NSRectEdge has a static var called max. I think you should just use that. For example:
popover.showRelativeToRect(rect, ofView: view, preferredEdge: NSRectEdge.max)

(The below is true for beta 5 (developing for 10.9). Your mileage may vary.)
Above, you get a solution that works - passing 0-3 where the function calls for a NSRectEdge. I'd like to pick up on
how can I know about what it is all about?
The documentation indicates that NSRectEdge is bridged with CGRectEdge, but trying to pass in CGRectEdge led to an error of
CGRectEdge is not convertible to NSRectEdge
NSRectEdge.from(2) or NSRectEdge(2) - as suggested above - come back with the error that
NSRectEdge has no accessible initializers
When you type NSRectEdge into Xcode, however, it shows two types:
CGRectEdge (which in this case is useless, see above) and Int.
I'm afraid that you need to guess that NSRectEdge is an enum when you see it both called as a string and defined as an int; but from there to looking up its definition in the framework is a logical step; as is guessing that you will have four values, one for each edge.
Incidentally, there seems to be an internal modulo operation going on somewhere - when you pass it '4', you get NSMinXEdge again and so on.

Related

Is it possible to use pyobjc with a privilved XPC helper tool and XPCInterface API?

I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:#protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(#"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(#"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a #selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom #protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'#'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v#:##?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface..
I've filed an issue for this in PyObjC's tracker: https://github.com/ronaldoussoren/pyobjc/issues/256.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://github.com/ronaldoussoren/pyobjc/blob/415d8a642a1af7f2bd7285335470098af4962dae/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol") to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
This solution is now documented in the official documentation, here: https://pyobjc.readthedocs.io/en/latest/notes/using-nsxpcinterface.html
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev#lists.sourceforge.net (PyObjC mailinglist).

NSMultipleValuesMarker use of undeclared type in Swift 3

I'm working on a mixed Objective-C / Swift macOS app.
I'm writing some code that checks the value returned from the selection key on an arrayController. NSArrayControllers return Any and normally I would check for multiple selection by seeing if the Any is a NSMultipleValuesMarker. However, writing this in Swift 3 I get the error:
Use of undeclared type 'NSMultipleValuesMarker'
The code I'm attempting is:
var selection = arrayController.value(forKeyPath: "selection.image")
if selection is NSMultipleValuesMarker {
// Do something for this case
}
I can't figure out what I'm missing to have NSMultipleValuesMarker available. I've tried importing Foundation, Cocoa and AppKit, but none of them seem to make the error go away.
'is' is the type check operator to check whether an instance is of a certain subclass type. Use selection === NSMultipleValuesMarker to check if selection is NSMultipleValuesMarker.

Array becomes inaccessible during burn API notification chain

I’m writing an app that burns to optical disks and there’s a strange problem when returning from the burn progress panel.
I call the disk recording setup sheet like this:
let setup_panel = DRBurnSetupPanel()
let passed = UnsafeMutableRawPointer(Unmanaged.passUnretained(self.disk_buckets[current_burn_disk]).toOpaque())
setup_panel.beginSetupSheet(for: self.window, modalDelegate: self, didEnd: #selector(self.burnSetupPanelDidEnd(_:return_code:context_info:)), contextInfo: passed)
disk_buckets is an array of classes each containing a reference to a DRTrack to be burnt - I pass an element so that I can deal with the DRTrack reference later.
After the setup panel is dismissed, the following method is then called:
func burnSetupPanelDidEnd(_ panel: DRBurnSetupPanel, return_code: Int, context_info: UnsafeMutableRawPointer) {
//…. some code ….
var track:DRTrack = self.disk_buckets[self.current_burn_disk].vDRTrack!
NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidEnd), name: NSNotification.Name(rawValue: BTVDiscBurnDidEndNotification), object: nil)
self.objc_panel.presentDiscProgressPanel(self.window, burner: panel.burnObject(), layout: track!)
}
objc_panel is a obj-c singleton that initialises and presents a disc burning progress panel (I did it this way as doing it in Swift is buggy - the progress panel is displayed as a tiny window instead of the default OS one - bug report has been sent to Apple). At the end of the burn objc_panel posts the BTVDiscBurnDidEndNotification notification to let the AppDelegate know it’s finished and so to call:
func progressDidEnd(_ note: Notification?) {
print (“DEBUG count: \(self.disk_buckets.count)")
print (“DEBUG first element: \(self.disk_buckets[0])")
//…. more code ….
}
All goes well until the end of the chain when any attempt to access the first element (or any, for that matter) causes a crash:
2016-10-04 14:01:34.654 DiskSpan[4025:220213] *** -[DiskSpan.BackupDisk retain]: message sent to deallocated instance 0x6000cafc0f40
(BackupDisk being the class that populates the array)
However, the first line prints out a correct count of the array i.e. 1. So how come I can't access anything in the array during a notification post?
EDIT: I tried appending a dummy item to the array before reading it back and still the crash only occurs when trying to read the array - not write to it.
It should be noted that this worked under Swift 2 and has only become a problem since Swift 3!
It turns out to be a retention problem. After the call to the obj-c class to start the setup window for burning, the elements in disk_buckets are all over-released. It was a hack but I enumerated over the elements and assigned them to a new array just before calling the setup panel.

NSTextView's insertText method is deprecated in OS X v10.11. What is the replacement?

I saw in the AppKit API Reference that the insertText method is deprecated in OS X v10.11. What am I supposed to use as a replacement?
The documentation says
- (void)insertText:(id)aString
This method is the means by which text typed by the user enters an NSTextView. See the NSInputManager class and NSTextInput protocol specifications for more information.
...
In NSTextInput there is a note:
IMPORTANT
NSTextInput protocol is slated for deprecation. Please use NSTextInputClient protocol, introduced in OS X v10.5, as described in NSTextInputClient Protocol Reference.
In NSTextInputClient protocol there is a method
- (void)insertText:(id)aString
replacementRange:(NSRange)replacementRange
This seems to be the appropriate replacement
Old method
- (void)insertText:(id)aString
New method
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
Using the new insertText method in practice
// Start String: abcdef
// Result: a++bcdef
[NSTextView insertText:#"++" replacementRange:NSMakeRange(1, 0)];
// Result: a++def
[NSTextView insertText:#"++" replacementRange:NSMakeRange(1, 2)];
// Result: a++f
[NSTextView insertText:#"++" replacementRange:NSMakeRange(1, 4)];
BTW, if you are pasting text to the NSTextView, Please change to use this:
pasteAsPlainText:
Apple document states it in this way
Discussion
This method behaves analogously to insertText:

Swift 2, warning: could not load any Objective-C class information from the dyld shared cache

I have found a few questions regarding this issue, yet none of them were helping with my problem. I am trying to save an object to core data using this code (which worked perfectly fine in Xcode 6 and Simulator...):
let fetchRequest = NSFetchRequest(entityName: "Patient")
let fetchedResults : [NSManagedObject]!
do {
fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
patienten = fetchedResults
} catch {
print("error")
}
I added the do-try-catch once I started working on this project in the Xcode 7 beta and a physical device.
Now, when I hit the Save button, this piece of code is called, the app freezes and I get the following:
warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.
Does anybody know where I went wrong?
For anyone coming across this in the future, I just ran into this problem myself and it turned out that I was actually getting a stack overflow from a recursive function.
Apparently calling setValue:forKey: on an NSObject calls the respective set[Key] function, where [Key] is the (capitalized) name you gave to the forKey section.
So, if like me, you have code that looks like the following, it will cause an infinite loop and crash.
func setName(name: String) {
self.setValue(name, forKey: "name")
}
Choose Product > Clean
I had similar issue. I deleted the app from the device. Then "Product->Clean" in the XCode menu. When I ran the app again, the issue got resolved.
Swift 3:
Actually this problem happened often when you have any property in input declared as type NSError and the compiler expect an Error output, so change the input type to Error usually solve this issue.
What helped me in similar problem (xCode 7, Swift 2):
reading this question
Or more quickly without explaining the reason of solution: just comment #objc(className) in your NSManagedObjectSubclass , that was generated from your CoreData Entity (#objc(Patient) - in your case ).
This solution (if issue still appears) does not applicable to xCode 7.1/Swift 2.1, as the way of generating NSManagedObjectSubclasses was changed.
Don't forget about cleaning your project (Product > Clean) and deleting the app from your device/simulator to replace CoreData storage on it.
let fetchRequest = NSFetchRequest(entityName: "Patient")
do {
let fetchedResults = try managedObjectContext!.executeFetchRequest(fetchRequest)
print("\(fetchedResults)")
} catch {
print("error")
}
The above code worked for me.
Maybe the issue maybe with how your core data is managed.
Check if your managedObjectContext is actually getting created.
Check the modelling of your core data

Resources