"Ambiguous use of 'children'" when trying to use NSTreeController.arrangedObjects in Swift 3.0 - swift

I get an Ambiguous use of 'children' error in XCode 8.0/Swift 3.0 when trying to send a message to the opaque NSTreeController.arrangedObjects object.
Here is a bare playground showing the use case :
import AppKit
extension NSTreeController {
func whatever () {
let k = (self.arrangedObjects as AnyObject).children // error here
}
}
I try to use AnyObject as a bridge to the underlying ObjC object, which is supposed to be able to get through any method call, I guess.
Xcode signals that it found two candidates that could respond to a "children" message: Foundation.XMLNode and AppKit.NSTreeNode.
Of course the obvious solution (casting to NSTreeNode) is not working because arrangedObjects returns an opaque, proxy object not a real NSTreeNode
Any suggestion on how we're supposed to use NSTreeController.arrangedObjects.children in Swift 3 ?

The two candidates for the children property differ by their type:
Foundation.XMLNode:137:14: note: found this candidate
open var children: [XMLNode]? { get }
^
AppKit.NSTreeNode:12:14: note: found this candidate
open var children: [NSTreeNode]? { get }
^
You can resolve the ambiguity by casting the value of the property
to the expected type. In your case:
let k = (self.arrangedObjects as AnyObject).children as [NSTreeNode]?

Another solution: Adding an Obj-C category to NSTreeController
.h
#import <Cocoa/Cocoa.h>
#interface NSTreeController (RootNodes_m)
- (NSArray *) rootNodes;
#end
.m
#import "NSTreeController+RootNodes_m.h"
#implementation NSTreeController (RootNodes_m)
- (NSArray *) rootNodes {
NSObject * arranged = self.arrangedObjects;
if ([arranged respondsToSelector: #selector(childNodes)]) {
return [arranged performSelector:#selector(childNodes)];
}
return nil;
}
#end
Now in your code you can use it like this:
return treeController.rootNodes() as? [NSTreeNode]
I had problem with the above answer: The compiler refused to compile when "whole module optimization" was turned on. A swift extension didn't help. I'm using Xcode 8.2.1.

Related

Objective-C interface Named "Category" cannot be imported into swift via the bridging header

The below swift code throws this error ('Category' is ambiguous for type lookup in this context)
// ctx is the UIContext, fetchCategories is an Obj-C function that queries CoreData
//for the Categories, the cast shouldn't even be necessary but swift will not
//recognize my obj-c interface
if let cats = self.fetchCategories(ctx) {
let array = cats as! [Category] //error happens on this line
return array
}
This is the implementation in my bridging header,
there are many other imports but I removed them for this post, so note that it is a fully functional bridging header for almost 40 other .h files
#ifndef AppDataRoom_Bridging_Header_h
#define AppDataRoom_Bridging_Header_h
#import "Category.h"
#endif /* AppDataRoom_Bridging_Header_h */
Below is the interface implementation
#interface Category : _Category <QLPreviewItem> {
UIImage *squareThumb;
}
I realize that changing the name of the Category class would be the best option since this is probably due to the naming convention being common, However when I change the code to this :
if let cats = self.fetchCategories(ctx) {
let array = cats as! [<ModuleName>.Category] //error happens on this line
return array
}
Is still says it is too ambiguous to lookup. Any ideas on other things I could try besides changing the interface name, because that really isn't an option and I will spare you all the reasons why.

Array in generics declaration in Swift

I came from Kotlin/Java to swift. I can't find anywhere how to make smth like
let response: InspResponse<[Album]>
I get an error message from xcode - response requires that '[Album]' be a class type
How can I specify response of list of objects in swift generics?
Where as response is an Objective C class compiled from Kotlin native
__attribute__((swift_name("InspResponse")))
#interface MusicFeatureInspResponse<T> : MusicFeatureBase
#end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("InspResponseData")))
#interface MusicFeatureInspResponseData<T> : MusicFeatureInspResponse<T>
- (instancetype)initWithData:(T _Nullable)data __attribute__((swift_name("init(data:)"))) __attribute__((objc_designated_initializer));
#property (readonly) T _Nullable data __attribute__((swift_name("data")));
#end;
I need an equivalent of kotlin val response: Reponse<List<Album>> in swift 5
For single:
let response = Response<Album>()
For array:
let response = Response<[Album]>()
See this for details.

Crash detect swift

NSSetUncaughtExceptionHandler catches only Objective-C exceptions. I need to catch only swift exceptions. Is it possible?
NSSetUncaughtExceptionHandler catches this one. let arr = NSArray() let x = arr[4]
I also want to catch this crash. let number: Int? = nil let val = number!
As I already mentioned in my comment above
Swift does not deal with exception, it deals with error, hence when you use try catch block of swift for let x = arr[4] you cant catch the exceptions (Its array out of bound exception not an error).
As Vadian also pointed out Swift encourages you to catch and solve those exceptions rather than catching them at run time.
That being said, there might be scenarios when you have to deal with exceptions explicitly, example you are dealing with third party framework which is written in Objective-C and might throw an exception or you might be dealing with certain iOS API which still runs in Objective-C runtime and throws exception in such cases, its handy to have a have method of your own in objective C and pass swift statements to it using block / closure and execute them inside Objective-C try catch block,
How can you do it?
Add an Objective-C file to your swift project, it will ask you should it create bridging header as well, say yes, update your .h and .m files as shown
ExceptionCatcher.h
#import <Foundation/Foundation.h>
#interface ExceptionCatcher : NSObject
+ (void)catchExceptionIfAny: (void(^)(void)) executeCodeBlock withCompletion: (void (^)(NSException *)) completion;
#end
ExceptionCatcher.m
#import <Foundation/Foundation.h>
#import "ExceptionCatcher.h"
#implementation ExceptionCatcher : NSObject
+ (void)catchExceptionIfAny: (void(^)(void)) executeCodeBlock withCompletion: (void (^)(NSException *)) completion {
#try {
executeCodeBlock();
}
#catch (NSException *exception) {
NSLog(#"%#", exception.description);
completion(exception);
}
}
#end
Finally, update your bridging header file with
#import "ExceptionCatcher.h"
And come to swift file and use it as
let x = NSArray()
ExceptionCatcher.catchExceptionIfAny({[weak self] in
let a = x[4]
debugPrint(a)
}) { (exception) in
if let e = exception {
debugPrint(e.description)
}
}
As you can see obviously x[4] should end up with array out of bound exception, now instead of crashing exception will be captured and returned to your completion block.
The code above works because x is declared as NSArray which is extended form NSObject if you use x = [1,2,3] you will be creating an instance of Swift Array and obviously swift array wont throw exception. So be very careful when you use this method.
This is not a work around to catch all sort of Exceptions and should be used sparingly (only in cases when avoiding it is impossible) This is an overkill and unnecessary for this usecase however

Swift is using the wrong subscript overload. How can I get it to use the right one?

I'm writing UI testing code using XCTest. Here is my test method:
func testLandingUI() {
let app = XCUIApplication()
let headerExpectedMessage = "Title"
let headerLabel = app.staticTexts[headerExpectedMessage]
XCTAssert(headerLabel.exists)
}
I'm getting this error:
ExampleUITests.swift:38:19: error: value of type 'Any?' has no member 'exists'
XCTAssert(headerLabel.exists)
^~~~~~~~~~~ ~~~~~~
What's strange about this error is that I expected headerLabel to be of type XCUIElement, not Any?.
XCUIApplication.staticTexts is an XCUIElementQuery, which has a subscript method declared thusly:
open class XCUIElementQuery : NSObject, XCUIElementTypeQueryProvider {
...
open subscript(key: String) -> XCUIElement { get }
...
}
What I believe is happening is the subscript method in XCUIElementQuery is not being selected by Swift's overload resolution. Instead, it's selecting this category on NSObject:
#interface NSObject (MyCategory)
- (id)objectForKeyedSubscript:(NSString*)key;
- (void)setObject:(id)obj forKeyedSubscript:(NSString*)key;
#end
I verified that if I remove that category from my project, the error goes away. Assume that removing that category is not possible (because it's not). Is there any way to get Swift to use the correct subscript method?
Minimal test case that shows the problem: https://www.dropbox.com/s/f0fm5ennco7t2ua/SubscriptCategoryTest.zip?dl=1
(note that the error is in the UI tests, so press command-shift-U to see the error)
EDIT: It looks like the problem only shows up if the category defines setObject:forKeyedSubscript:. Interestingly, I get a slightly different error if both getter and setter are defined vs. just the setter.
Since the compiler is confused, you need to help it....
Given this example:
let example1: Any? = "1"
let example2: Any = "2"
You have two issues (it looks like you have an optional...)
if example1.exists { // won't work - I got your error message
}
if (example2 as AnyObject).exists { // works with a cast
}
I believe if you correct identify the type of the variable it will solve your problem. It is matching the NSObject category only because it didn't match something more specific.

Instance method not found (return type defaults to id)

I am taking a warning from Xcode. Here is the code
DeviceList *dList = (DeviceList* )[[User thisUser] devices];
[dList getListId];
The warning states that instance method -getListId is not found. However the method exists in my source code
- (NSString*) getListId
{
T
if ( ... != nil)
{
return ...;
}
else
{
return #"";
}
}
I cannot figure out what the problem is when I am calling the method.
have you added a declaration for this method in the .h file, and if so, have you imported the .h into the file you are trying to call this method?
this error is basically the compiler saying it can't find the method declaration, so it doesn't know what to expect the return type to be.
in your DeviceList.h , make sure you have
#interface DeviceList : Parent
- (NSString*) getListId;
..
..
#end
the warning occurs when your method is not declared in your header file and you try to call it outside your (self) class.