I am willing to get a reference to an instance of an Objective-C class by calling, from Swift, one of its class initialiser functions.
The class I am willing to use is AWSMobileAnalytics, available at https://github.com/aws/aws-sdk-ios/blob/master/AWSCore/MobileAnalytics/include/AWSMobileAnalytics.h
It defines a initialiser like this
#interface AWSMobileAnalytics : NSObject
+ (instancetype)mobileAnalyticsForAppId:(NSString *)appId;
...
#end
My code is
let ma = AWSMobileAnalytics.mobileAnalytics(forAppId: appId)
(and I tried many variant of it). The compiler says
Type AWSMobileAnalytics.Type has no member 'mobileAnalytics'
Any suggestion ?
Seb
Make sure you have imported the framework in bridging header.You need to call like this
let ma = ViewController.mobileAnalyticsForAppId("abc")
There's no need to use a Bridging Header.
Just import it like this in your AppDelegate:
import AWSMobileAnalytics
then
AWSMobileAnalytics(forAppId: "yourID")
Related
I have created a class in Swift (which I'm learning) that I use to hold certain values as follows:
#objc public class Book: NSObject {
var title: String = ""
var date: Date = Date()
var sku: Float = 0
}
I would now like to store this class in the equivalent of a property that can be accessed by my Objective-C class.
I tried creating a variable in the View Controller where the class is used as follows:
var lastBookViewed = Book()
However, when I try to save the object to the property with the followng code, I get an error:
//create instance of book
let myBook = Book()
//gather information about book
lastBookViewed = myBook //THROWS COMPILER ERROR 'Use of Unresolved Identifier lastBookViewed
Is there a way to do this?
If easier, storing the object to a property in the Objective-C file would work as well but so far I have not been able to get the Objective-C file to recognize the Book class created in Swift although I put #objc before it as recommended.
Edit
When I try to create a property or variable for storing the custom swift object in a different Swift class (the View Controller class) in the same Swift file with the following syntax:
var lastBookViewed: Book
the compiler gives a warning for the VC class "yourVC has no initializers"
Go to your project’s general settings. Select the proper target for your app. Go to “Build Settings” and switch to “All”, instead of “Basic” which is the default. Here search for the “Packaging” section. Turn on “Defines Module”, by changing “No” to “Yes”.
When this is turned on we will now be able to use swift classes inside
of objective-c files.
Before leaving the “Build Settings” look for “Product Module Name” in the “Packaging” section. This will be important so make sure to take note of and copy the “Product Module Name” exactly.
Next go to an objective-c class where you would like to be able to have access to your swift class. In the implementation or .m file of this class import a header like this:
#import "MyProjectModuleName-Swift.h"
Here the file name of the import must be the exact Project Module Name from the build settings. Any objective-c file where you want access to your swift class should include this import.
Now it is worth mentioning one potential issue that may arise when using a swift class. Objective-c cannot read top-level swift classes. So if you go to use a method or variable from your swift class directly it will not be recognized. There are one simple solution to this issue. It’s to make your class public
#objc public class myClass
Im in a situation where I need to use Objective-C category to extend a Swift class. I've done something as follows:
In "SomeClass.swift":
class SomeClass: NSObject {
}
In "SomeClass+Extension.h":
#import "Project-Swift.h"
#interface SomeClass (Extension)
-(void)someMethod();
#end
This has worked well. And if I try to use the SomeClass extension in my Objective C code, it is fine.
The problem is, if I want to use someMethod() in a another Swift class, I will need to put the SomeClass+Extension.h file into my ObjC-BridgingHeader.h file.
But doing this will cause a circular dependency, because SomeClass+Extension.h also imports Project-Swift.h.
Does anyone have a good way to get around this?
Please note that simply forward declaring the class in the category header will not work, as categories cannot use forward declarations for it's own implementation as so:
#class SomeClass without importing Project-Swift.h will give a compile error.
The Bad
i too have been fighting this issue a bunch. unfortunately the documentation pretty explicitly states that this pattern is not allowed:
To avoid cyclical references, don’t import Swift code into an
Objective-C header (.h) file. Instead, you can forward declare a Swift
class or protocol to reference it in an Objective-C interface.
Forward declarations of Swift classes and protocols can only be used
as types for method and property declarations.
also throughout the the linked page you will notice it keeps mentioning to import the generated header specifically into the .m file:
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m file
within that target
The Good
one solution that may work for you is to create a swift extension that redefines each method you need in the category. it is fragile and ugly, but arguably the cleanest solution.
/**
Add category methods from objc here (since circular references prohibit the ObjC extension file)
*/
extension SomeClass {
#nonobjc func someMethod() {
self.performSelector(Selector("someMethod"))
}
}
adding the #noobjc to the front allows the
same method signature to be used w/o overriding the ObjC implementation
now the import "SomeClass+Extension.h" from the bridging
header can be removed
if support for more than two input params is needed, or tighter type coupling is desired i would recommend using the runtime to call the underlying function. a great description is here.
From the Interoperability guide, we cannot directly access the subclassed / categorized / extensioned Objc-objects for the .swift [SomeClass] class.
But as a turn-around, we can do this:
For Variables , we can do this:
extension Class {
private struct AssociatedKeys {
static var DescriptiveName = "sh_DescriptiveName"
}
var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
}
For Methods, we can use method_swizzling which is not recommended.
As one simple solution, you can move the extension to your Swift code. Then you won't have any dependency problems.
I am trying to extend Objective C class in my Swift class. This is where I got so far:
SINMessageClientDelegate is Objective C class. ViewController is written in Swift. I already have Bridging Header, so I can use Objective C object in my Swift class.
This is how my Swift code class definition looks like:
class ViewController: UIViewController, SINMessageClientDelegate {
I am getting the following error:
Type "ViewController does not conform to protocol SINMessageClientDelegate"
This is how definition of SINMessageClientDelegate looks like:
#protocol SINMessageClientDelegate <NSObject>
- (void)messageClient:(id<SINMessageClient>)messageClient didReceiveIncomingMessage:(id<SINMessage>)message;
- (void)messageSent:(id<SINMessage>)message recipientId:(NSString *)recipientId;
- (void)messageDelivered:(id<SINMessageDeliveryInfo>)info;
- (void)messageFailed:(id<SINMessage>)message info:(id<SINMessageFailureInfo>)messageFailureInfo;
I tried to create these methods using Swift in ViewController:
// Tells the delegate that a message has been received.
func messageClient(id: SINMessageClient, didReceiveIncomingMessage:SINMessage)
{
}
// Tells the delegate that a message for a specific recipient has been sent by the local user.
func messageSent(id: SINMessage, recipientId: NSString)
{
}
// Tells the delegate that a message has been delivered (to a particular recipient).
func messageDelivered(id: SINMessageDeliveryInfo)
{
}
func messageFailed(id: SINMessage, info: SINMessageFailureInfo)
{
}
Can someone advice what is the proper way of doing this, since I am getting the same error after adding my code?
Thanks!
One problem in the way you pose your question is that you're using the wrong words. You are not "extending a class". You are conforming to (or adopting) a protocol.
The trouble, however, is that you are not adopting it (conforming to it), as the error message rightly tells you. The reason apparently is that you don't know how to read Objective-C. You'll need to learn to do that in order to proceed. For example, given this Objective-C declaration:
- (void)messageClient:(id<SINMessageClient>)messageClient
didReceiveIncomingMessage:(id<SINMessage>)message;
The Swift implementation will need to be:
func messageClient(SINMessageClient,
didReceiveIncomingMessage message: SINMessage) { /* ... */ }
Whereas what you have is not at all the same thing. You have this:
func messageClient(id: SINMessageClient,
didReceiveIncomingMessage:SINMessage) { /* ... */ }
That is not a match, so you are not implementing the required method, but rather some totally different method. That's legal, but it has nothing to do with the protocol you are supposed to be conforming to. And the same for the rest of your declarations.
I have a swift class defined in a framework that is being used from an obj-c app.
The generated -Swift.h header contains the swift classes marked with #objc but there's one property that makes the compilation fail.
This property is defined like this in swift code :
public var storageClass : StorageProtocol.Type = UserDefaultStorage.self
and so the generated obj-c property looks like this
#property (nonatomic) Class <StorageProtocol> __nonnull storageClass;
But Xcode does not accepts the "StorageProtocol" symbol here, because the forward declaration "#protocol StorageProtocol;" is missing.
If I add a new var defined like this :
public var storage : StorageProtocol? = nil
The forward declaration is added on top of class that define these properties and the -Swift.h compilation succeed.
So it looks like a bug in the -Swift.h generation, but is there another way to force that forward declaration without using a dummy var ?
I did not find any other way than using a dummy var...
When you implement a class MyGreatClass in Swift its fully qualified name will by <MyPackageName>.MyGreatClass. This is different to Objective-C, where the fully qualified name of that same class is MyGreatClass.
Unfortunately this introduces a problem for me. When I am using NSUnarchiver and the archive was written with Objective-C objects I cannot unpack it with Swift-classes(see below for a detailed description).
This means I need to find a way to rename the namespace for my Swift classes. How do I do that?
Any help would be great!
Background: Why can't NSUnarchiver see/load my swift class?
I have implemented a small program to read a file, which was archived with NSArchive.
These are my files:
main.swift:
import Foundation
// parse command line - total path to *.trace file (from Apple's Instruments app)
var traceFilePath = Process.arguments[1]
var traceFile = NSURL(fileURLWithPath: traceFilePath)
var error:NSError?
// check if the file exists
if (traceFile?.checkResourceIsReachableAndReturnError(&error) == false){
// file does not exist or cannot be accessed
println("\(error)")
exit(1)
}
var rawData = NSData(contentsOfURL: traceFile!)
var data = NSUnarchiver(forReadingWithData: rawData!)
var decodedObject: AnyObject? = data?.decodeObject()
XRObjectAllocRun.swift:
import Foundation
class XRObjectAllocRun: NSObject {
// class to be implemented
}
When I now run my application on an Instruments-file I am getting the following error: Terminating app due to uncaught exception 'NSArchiverArchiveInconsistency', reason: '*** class error for 'XRObjectAllocRun': class not loaded'.
This is really strange because when I add the exact same class in an Objective-C file with a bridging header file I have no issues.
trace file reader-Bridging-Header.h: is empty.
XRObjectAllocRun.h:
#import <Foundation/Foundation.h>
#interface XRObjectAllocRun : NSObject
#end
XRObjectAllocRun.m:
#import "XRObjectAllocRun.h"
#implementation XRObjectAllocRun
#end
What am I missing? Why is my Objective-C class found, whereas my Swift class is not?
Swift has no issues for example with var x = XRObjectAllocRun() in main.swift, but yet the NSUnarchiver still complaints about a missing XRObjectAllocRun class when I stay purely within Swift. Is the NSUnarchiver looking in the wrong places - does it for some reason only accept Objective-C classes?
If you want to know what I am trying to do check this stackoverflow question out.
Update
This is what apple writes:
Swift classes are namespaced based on the module they are compiled in, even when used from Objective-C code. Unlike Objective-C, where all classes are part of a global namespace
Further more:
For example, when you create a document–based Mac app, you provide the name of your NSDocument subclass in your app’s Info.plist file. In Swift, you must use the full name of your document subclass, including the module name derived from the name of your app or framework.
Yikes, trying to figure out the mess now...
Try this when you declare your class:
#objc(XRObjectAllocRun) class XRObjectAllocRun: NSObject {
// class to be implemented
}
That will give this class the same name as the archived class, namely XRObjectAllocRun, instead of the namespaced Swift name trace_file_reader.XRObjectAllocRun.
This is always a concern when you're translating from Objective-C to Swift and you've got an existing archive to deal with. See Apple's documentation:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html
Note the discussion under "Exposing Swift Interfaces in Objective-C".