I'm having problems importing a class (which is written in Swift and from a pod) into my Objective-C code. I think the problem is due to the class failing to appear in my autogenerated -Swift.h header file, but I don't know how to fix this.
My Podfile has the use_frameworks! declaration and I have the pod installed and I'm able to use the pod's class in any of my Swift code but not in Obj-C.
The Swift class I'm trying to import is an extension of UITableViewCell, which is declared as a public class. It didn't have the class declared with the #objc annotation in its source but adding it myself didn't seem to make a difference either.
In my Obj-C source I have #import "ProjectName-Swift.h", (with my actual project name) but my Obj-C file doesn't still doesn't recognise the class after a clean and build. I have checked that the Swift header file exists (and it does) but upon inspection it doesn't seem to have any mention of the Swift class that I'm trying to import.
I tried putting in a forward #class declaration to the class in my Obj-C source, but this only removed the warning and delayed the error until I try to build. Modules are enabled in my build settings. If anyone has some insight, your help would be much appreciated.
Edit:
If I manually add the source file to Build Phases -> Compile Sources for my project, then everything works - but of course this defeats the purpose of using a pod. So this seems to be an XCode search path issue.
Only public classes and methods are published to be used from other modules. To ensure Objective-C compatibility, you can annotate your classes with #objc and inherit NSObject.
For example, change your class definition from:
class MyClass {
func sayHello() {}
}
To:
#objc public class MyClass: NSObject {
public func sayHello() {}
}
To use Swift frameworks (iOS 8+) in Objective-C on XCode 7, you just have to import them like you would in Swift using the #import statement:
#import MyModule;
And all your public classes will be available.
More information in 'Importing External Frameworks' and 'Using Swift from Objective-C' sections of the Swift and Objective-C in the Same Project apple docs.
I ended up finding a solution. It seems that Swift pods aren't added to the Project-Name-Swift.h, but instead a separate Swift header is generated for each module.
What I needed to do was import the following Swift header file.
#import "Module_Name/Module_Name-Swift.h"
An alternate solution that eluded me was pointed out by redent84. You just need to put the following in your Objective-C source as you would in Swift.
#import Module_Name;
which is probably equivalent to the swift header import once the sugar is stripped away.
Related
I am trying to understand the best ways in which to bring native library code into a NativeScript plugin for iOS. I've had success in past bringing in a CocoaPod and accessing the symbols from that. So I want to create my own.
I follow the process for pod lib create TestPod to generate my library project. (https://guides.cocoapods.org/making/using-pod-lib-create.html)
The 'library' is a trivial test: it simply creates a class with a function that returns
a recognizable greeting string.
The associated "Example app" demo produces a text label that displays this string.
This all works as expected, all running in pure iOS world (written in Swift).
At the Nativescript side, I'm using the Nativescript Plugin Seed and I'm declaring my Podfile in the src/platforms/iOS folder as directed. My "plugin" (trivial as it is as a test), has the iOS-specific parts in the pluginName.ios.js file. I have a separate test method in here that verifies I can reach and use native iOS platform symbols (e.g. NSMutableString) and that works as expected. I want to do the same thing with native code imported from my library.
But when I bring the Podfile into Nativescript, it builds okay, but I'm not able to see any of the symbols as I would expect to see.
I generate typings and I don't find them either, but I do find a "TestPod.d.ts" typings file that declares some version info and a mysterious class named "UITest" that I did not define and bears no resemblance to my "SimpleTest" class, which I can't find anywhere.
I'm sure I'm missing something here that is probably obvious to the knowledgeable. But I'm unable to guess what it is. Any ideas?
import Foundation
#objc public class SimpleTest : NSObject {
public func announce() -> String {
return "Greetings from Swift code in a library"
}
}
I'm calling it in the plugin using the following:
public testNativeLib() : string {
let str:string;
try {
const testClass = new SimpleTest()
str = testClass.announce()
} catch(e) {
str = e.message;
}
return str;
}
and what I get returned for str is the catch case error message: "Can't find variable: SimpleTest"
Without seeing the source to the pod file; the only guess would be that you didn't use the #objc on anything that you wanted exposed. Without you exposing anything with #objc (or descending from a native objc class, so it is tagged by swift automagically) NativeScript cannot see it.
Please note their are some types in Swift that currently cannot be consumed by NativeScript (Or ObjC) and you have to write some wrapper code in swift around it using types that are compatible with ObjC so that NativeScript can use it.
Please see: https://docs.nativescript.org/guides/ios-source-code for more info on requirements...
The code needs to be:
import Foundation
public class SimpleTest : NSObject {
#objc public func announce() -> String {
return "Greetings from Swift code in a library"
}
}
Move the #objc to the actual property/function you want exposed. The NSObject will already cause the class to be exposed if it has exposed members.
The metadata generated:
A complete demo using a swift source file in a plugin is now located at
https://github.com/NathanaelA/demo-swift-plugin
If I have full control over the source, then I won't normally add another moving piece (i.e. cocoapod) to the mix; just let xcode compile the swift code and expose it, no need to add any additional places something can break.
However, if you want to see how to do an actual swift CocoaPod plugin; checkout any of these plugin repos that use swift code and cocoapod:
https://github.com/tomvardasca/nativescript-crypto
https://github.com/arpit2438735/nativescript-tglib
https://github.com/Daltron/NotificationBanner
In the case of an actual cocoapod; you need to have a valid Podfile, and a valid podspec file! Once you have those; when you build the application, you can open up xcode and verify that the cocoapod is linked in. If it isn't linked in then your podfile/podspec is messed up and has to do with a issue with Cocoapod and it is not a NativeScript issue. So in that case; you need to follow some Cocoapod tutorials to get it to work.
Please note; nuking your platform folder frequently while you are testing with cocoapods is highly recommended. Occasionally Nativescript does not detect the changes to Native code properly and so it then doesn't rebuild the xcode project/workspace files. So nuking your platforms folder will of course force it to rebuild them. If you don't you WILL waste a lot of time while messing around with cocoapods.
When running the sample project you will see this:
In addition, in the demo, the metadata generated from the code I saved in the demo/metadata folder so you can look at it.
I'm trying to use Hyperloop in order to use a third-party open source library. The library I want to use is Sweet Alert iOS.
The library has a swift file. I have put this file inside an src directory inside my project's main folder (does it have to be inside src folder? can I use other folder in my project? what about sub-directories?).
According to the Titanium documentation:
Any *.swift files found in your src directories will automatically be compiled
In my app when I do something like this:
var UIView = require('UIKit/UIView');
Ti.API.info('UIView => ' + UIView);
It works. But if I try to require any of the classes in the swift file I get an error that it can't find architecture x86_64 module.
var sweetAlert = require('SweetAlert');
var cancelAnimatedView = require('CancelAnimatedView');
When looking at the SweetAlert source code, I also notice that SweetAlert class is open while other classes are just regular classes:
open class SweetAlert
I'm not a swift developer so I don't really know what it means.
What am I missing here?
EDIT
Some progress... So I found that I need to have an appc.js file that's defining the src frame work and a name for MyFramework. And that the require I do should match the filename of the swift file. Now when I compile I see some more interesting output about the swift compilation, but getting an error:
[INFO] Generating metabase for swift MyFramework /Users/ophir/Documents/Appcelerator_Studio_Workspace/HyperloopTest1/src/SweetAlert.swift
2017-06-29T14:43:44.061Z | ERROR | An uncaught exception was thrown!
Cannot read property '1' of null
2017-06-29T14:43:44.064Z | ERROR | Cannot read property '1' of null
EDIT 2
I've cleared all the script in the swift file, and narrowed it down to the following failing script:
import Foundation
import UIKit
import QuartzCore
public enum AlertStyle {
case success,error,warning,none
case customImag(imageFile:String)
}
class SweetAlert: UIViewController {
}
class AnimatableView: UIView {
func animate(){
}
}
If I remove the func animate() { } it will not fail. Even if this function is inside that one SweetAlert class then it will fail. And from what I've read this is a pretty standard swift code.
So this makes me wonder - is Hyperloop 2.0.0(!) ready for production apps?
I receive a similar error when creating a simple Swift class and including it in a Titanium/Appcelerator project with the appc.js file set up just how they show in the sparse documentation. The project will compile with no issues and run in the Xcode Simulator, as long as I don't reference the class from the Swift file. If I reference that class in the project, the error arises. Based on the compile log, Swift files do compile into the project, but the classes don't seem to properly link in the end for "require"-ing into the JavaScript code.
When I search online for solutions, this same issue shows up all over, and the Titanium/Appcelerator team never presents a solution. I've tried Titanium SDK 6.0.1, 6.1.2, and 6.2.0, with all the same results; I've tried adding Swift code to existing projects and to new ones; I've also uninstalled and reinstalled Titanium/Appcelerator. (I've even tried running their sample app with no luck!)
Hyperloop does work when I use native frameworks, like UIKit, but it doesn't seem ready for production apps for 3rd party frameworks and classes.
I hope my discoveries help you and others to debug projects, but, unfortunately, I don't think I will be able to offer much help outside that.
This is my Podfile
platform :ios, 8.0
use_frameworks!
pod "ChartboostSDK"
pod "SVProgressHUD"
SVProgressHUD is being turning into a framework, but for some reason Chartboost is not.
That of course makes the import for Chartboost not working in my code.
The library ChartboostSDK is not supported for Swift directly using CocoaPods, you need to integrate it manually in the following way:
1.First, unzip the SDK package and drop the Chartboost Framework into your Xcode project. Be sure you've also linked these frameworks:
StoreKit
Foundation
CoreGraphics
UIKit
2.Then you need to add a Swift bridging header allows you to communicate with your old Objective-C classes from your Swift classes.You will need one if you plan to keep portions of your codebase in Objective-C. You can create it manually like in the following way:
Add a new file to Xcode (File > New > File), then select “Source” and click “Header File“.
Name your file “YourProjectName-Bridging-Header.h”.
Navigate to your project build settings and find the “Swift Compiler – Code Generation” section. You may find it faster to type in “Swift Compiler” into the search box to narrow down the results. Note: If you don’t have a “Swift Compiler – Code Generation” section, this means you probably don’t have any Swift classes added to your project yet. Add a Swift file, then try again.
Next to “Objective-C Bridging Header” you will need to add the name/path of your header file. If your file resides in your project’s root folder simply put the name of the header file there. Examples: “ProjectName/ProjectName-Bridging-Header.h” or simply “ProjectName-Bridging-Header.h”.
Open up your newly created bridging header and import your Objective-C classes using #import statements. Any class listed in this file will be able to be accessed from your swift classes.
You bridging header must be have inside the following lines:
#import <UIKit/UIKit.h>
#import <Chartboost/Chartboost.h>
#import <Chartboost/CBNewsfeed.h>
#import <CommonCrypto/CommonDigest.h>
#import <AdSupport/AdSupport.h>
You can read more about the next steps using Charboost in his iOS Integration help manual.
I hope this help you.
I'm working on a 3rd party UIKit replacement for iOS. I have it building as a framework using a seriously helpful project from GitHub. (Not mine, but if you have interest, it's here.
I'm trying to use my library in other projects I'm writing. Like I said, it's basically a drop-in replacement for much of UIKit, so I decided to import the framework in my project's *.pch file instead of everywhere I might wish to use a button, action sheet, alert view, etc...
When I DON'T have an #import directive in a header file and declare a property of type MBMButton, the compiler gives me an error of "Unknown type name 'MBMButton'; did you mean 'UIButton'?" Oddly enough, the code will still run (even though this is an error, not a warning). Adding #class MBMButton or #import <MBMUIKit/MBMUIKit.h> resolves this compiler complaint.
When I DON'T have an #import directive in an implementation file (or its header) and call a method that exists in MBMUIButton but NOT in UIButton, I get a compiler error of "No visible #interface for 'UIButton' declares the selector...". As before, the code will actually run, since it's a valid call.
Now, I've done some digging, and I've changed my project's settings. Where I didn't have any value in the GCC_PREFIX_HEADER, I added the file name. Noting the Quick Help description by Apple, I tried both "ProjectName-Prefix.pch" and "./ProjectName-Prefix.pch". Neither seemed to resolve the problem. I eventually figured out that the Target-level settings override the Project-level settings, and that the Target-level settings already specified "ProjectName/ProjectName-Prefix.pch". So that was a dead end. (Nice to learn exactly what the relative path settings are, though!)
I'm OK with not using the *.pch file. It's only a convenience, and I can definitely use appropriate #class and #import directives. What's bugging me is not understanding the issue. How should one use the *.pch file?
The prefix header file is automatically included in every .m file in your project but not in any .h files.
Therefore, any references to classes will require the header to be included or a forward declaration:
#class MyClass;
However for #protocols you'll need the full header, a forward declaration won't work:
#protocol MyProtocol; //this won't work
#interface MyController : UIViewController <MyProtocol>
…
#end
I am trying to create and objective-c class (subclassed UIViewController) with xcode 4.4. I go to File->new and select an objective-c class template under ios/Cocoa Touch. It says that this selection will create "An Objective-C class, with implementation and header files.". But it only creates an implementation file. The implementation file has a #import for the missing header file.
I am using storyboards. I did not select "With XIB for user interface".
Am I doing something wrong? If this is a bug, any suggestions about how to get around it?
ok, I found a solution. First I deleted the header file that I saw with Finder in the project directory. Then I went into xcode and deleted the implementation file. Then I started over and re-created the objective-c class. This time the header file was created. Weird