My question is about a bridge header that does not seem to work in Swift 2. I copied this code strait from the Parse.com iOS guide into xCode to see if it would work.
#import <Parse/PFObject+Subclass.h>
class Armor : PFObject, PFSubclassing {
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Armor"
}
}
This doesn't work. I get an error on the #import <Parse/PFObject+Subclass.h> line with the error Consecutive statements of a line must be separated by ';'. So, my question is how I would go about subclassing in Parse.com with Swift 2. I have looked around the internet and haven't found anything. I think that there may have been a change in how Swift imports bridge headers, but I am not at all sure because I have never used a bridge header before. So, I could be doing something idiotic. Anyway, any help is greatly appreciated. Thanks.
First of all make sure you have the latest Parse SDK. If you have the latest SDK you can create a PFObject Subclass like this;
class Armor: PFObject, PFSubclassing {
static func parseClassName() -> String {
return "Armor"
}
}
When you create your custom Subclass, you should register your subclass to Parse SDK in AppDelegate like this;
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Registering SubClasses
Armor.registerSubclass()
// Initialize Parse.
Parse.enableLocalDatastore()
Parse.setApplicationId("Your API Key", clientKey: "Your Client Key")
return true
}
Post SDK release 1.14.0 you should not need to register subclasses.
See the changelog from https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/releases/tag/1.14.0.
There is also discussion of this issues in #1023 and 1035 where removing the calls to registerSubclass() has resolved looping problems in PFUser.
Related
This code is from a Swift project App delegate. It is used to help configure Stripe with a publishable key.
//Appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
//The code helps configure Stripe with a publishable key.
STPPaymentConfiguration.shared().publishableKey = Constants.publishableKey
...
}
Two errors are displayed when building the app after adding the Swift line to the Objective C App Delegate
//AppDelegate.h
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
STPPaymentConfiguration.shared().publishableKey = Constants.publishableKey
Property 'shared' not found on object of type 'STPPaymentConfiguration'
Use of undeclared identifier 'Constants'
This was a similar error in compiling before #objc was added to the demo Swift function, MockApiClient. Should it be added elsewhere? I've tried adding #objc to the enum as mentioned in the answer here to no avail yet.
//Constants.swift
//This is the file the original Swift app delegate accesses
import Foundation
enum Constants {
static let publishableKey = "pk_live_..."
static let baseURLString = "http://54.33.123.227:1234"
static let defaultCurrency = "usd"
static let defaultDescription = "Receipt" //change to describe actual app & charge
}
Steps taken:
Opened the Objective C project and created a bridging header
Created a demo class in Swift while still in the Obj C project to make sure it can be used, in this case to print from an Objective C file when the view is loaded. Specifically derived from an NSObject. Adding the override to the initializer and using the #objc prefix.
// MockApiClient.swift
import Foundation
class MockApiClient: NSObject
{
override init()
{
print("Initializer called in Mock API client")
}
#objc func executeRequest()
{
print("The execute request has been called in the Mock API Client")
}
}
//ViewController.h
//Prints the Swift request written in the MockApiClient the the view loads
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MockApiClient *client = [MockApiClient new];
[client executeRequest];
}
Copied the #import "ViewController.h" import to the automatically generated project-Bridging-Header.h file to expose the Objective C in it to swift
Added the necessary Swift files to the Objective C project so that the Constants.publishablekey data from Constants.swift can be found
How can this Swift App delegate code be added to the App delegate of an Objective C project?
Edit: error when adding #objc to the enum declaration in Constants.swift
Edit: error when adding #objc to the enum declaration in Constants.swift
Swift enums used as namespace cannot be exposed to Objective-C.
You may need to use class to make it work both for Swift and Objective-C:
#objcMembers
class Constants: NSObject {
static let publishableKey = "pk_live_..."
static let baseURLString = "http://54.33.123.227:1234"
static let defaultCurrency = "usd"
static let defaultDescription = "Receipt" //change to describe actual app & charge
private override init() {}
}
The ability of Objective-C to see things defined in Swift depends on the automatically generated header file. This is not the bridging header. It is a header file buried in your derived data called YourProject-Swift.h. Your Objective-C .m file needs to #import "YourProject-Swift.h" (using the correct name).
Then, your Swift things need to get into that file. For that to happen, they need to be of a type that Objective-C can see at all (i.e. classes) and they need to be explicitly exposed to Objective-C with appropriate #objc attributes.
I'm integrating GTM v5 (GTM + Firebase) in a Swift project, and I want to be able to call some methods when tags are triggered. However, it doesn't seem to work with Swift, although similar implementations in Objective C and Android projects did work.
Here's the class conforming to the TAGCustomFunction protocol :
import Foundation
import GoogleTagManager
final class Tags: NSObject, TAGCustomFunction {
func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
print("YEAH ! IT WORKS !")
return nil
}
}
Everything is working well, even though I see these kind of logs:
GoogleTagManager info: Processing logged event: applicationStart with parameters: (null)
But the log I'm printing is not showing…
I'm sure about the configuration of the container since this one is correctly loaded, and I use the exact same container for my Objective C project, in which it works perfectly.
I think TAGCustomFunction needs to have class/method #objc annotations, and the class-level #objc annotation needs to specify the class name, i.e.
import Foundation
import GoogleTagManager
#objc(Tags)
final class Tags: NSObject, TAGCustomFunction {
#objc func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
print("YEAH ! IT WORKS !")
return nil
}
}
After reading this SO post, I suddenly remembered that a Swift project could embed some objective C classes.
The solution to my problem was ridiculously easy to set up once I realized that, eventhough I've never had to do it before.
I created a new Cocoa Touch Class like the following :
Here is the .m :
#import "MyCustomTagClass.h"
#import "MySwiftClass-Swift.h"
#implementation MyCustomTagClass
- (NSObject*)executeWithParameters:(NSDictionary*)parameters {
[MySwiftClass myMethod];
}
#end
And here is the .h :
#import <Foundation/Foundation.h>
#import <GoogleTagManager/TAGCustomFunction.h>
#interface MyCustomTagClass : NSObject <TAGCustomFunction>
- (NSObject*)executeWithParameters:(NSDictionary*)parameters;
#end
Note that I import a header for my Swift class, which is automatically generated by Xcode. Just add -Swift.h after the name of your class to import it, just as I did in the .m example above.
Last, but not least, update your Swift class with #objc annotations at class and method declaration lines :
import Foundation
#objc class MySwiftClass: NSObject {
//...
#objc static func myMethod() {
// do something...
}
}
I hope this helped !
I have created a project to test the Typhoon framework , I have created two classes ApplicationAssembly and CoreAssembly where I inject some properties and constructors and a default Configuration.plist to load data from it.
ApplicationAssembly
public class ApplicationAssembly: TyphoonAssembly {
public dynamic func config() -> AnyObject {
return TyphoonDefinition.configDefinitionWithName("Config.plist")
}
}
CoreAssembly
public class CoreAssembly: TyphoonAssembly {
public dynamic func apiHandler() -> AnyObject {
return TyphoonDefinition.withClass(ApiHandler.self) {
(definition) in
definition.useInitializer("initWithDebugging:debugProcess:mainURL:") {
(initializer) in
initializer.injectParameterWith(TyphoonConfig("debug_mode"))
initializer.injectParameterWith(TyphoonConfig("debug_path"))
initializer.injectParameterWith(TyphoonConfig("api_url"))
}
definition.scope = TyphoonScope.Singleton
}
}
public dynamic func viewController() -> AnyObject {
return TyphoonDefinition.withClass(ViewController.self) {
(definition) in
definition.injectProperty("apiHandler", with:self.apiHandler())
}
}
}
I set in my Info.plist the TyphoonInitialAssemblies first the ApplicationAssembly and then the CoreAssembly.
Everything works fine without exceptions or anything except that the app never enters in AppDelegate neither in the ViewController class. I don't know maybe I missed something in the doc or anything.
What I'm missing here?
Why in debug not enter in the ViewController class that is the initial view controller in Storyboard?
The problem was that the ApiHandler class does not extend NSObject, which is a requirement. This is because Typhoon is an introspective Dependency Injection container. As Swift has no native introspection it uses the Objective-C run-time.
The App should not however have crashed in such an obfuscated way. I have opened an issue to look at how to fail with a meaningful error, rather than infinitely recurse.
After solving the initial problem, I also noted that the init method for ApiHandler passing in a Swift Bool object. This needs to be an NSNumber.
init(debugging : NSNumber, debugProcess : String, mainURL : String) {
self.debugging = debugging.boolValue
self.debugProcess = debugProcess
self.mainURL = mainURL
}
Given that Typhoon uses the Objective-C runtime, there are a few quirks to using it with Swift - the same kinds of rules outlined for using Swift with KVO apply.
I'm trying to pass a dictionary object to an Objective C protocol using swift.
the protocol code snippet is as follows:
#protocol MessageDelegate
- (void)handleNewMessageArrived:(NSDictionary *)messageContent;
#end
and this is the swift class the implements the protocol:
class ViewController: UIViewController, MessageDelegate
{
...
func handleNewMessageArrived(messageContent : NSDictionary!)
{
...
}
}
But the build fails, and the error I get is:
"the type 'ViewController' does not conform to protocol 'MessageDelegate"
I looked at this SO Question but it deals with a specific object type.
is there an error in the way I declare\implement the delegate method? or in the way I assume the arguments are mapped in swift?
I'm new to Swift so any Help will be much appreciated.
Try implementing the method in your Swift class like this:
func handleNewMessageArrived(messageContent: [NSObject : AnyObject]!) {
// Handle the message
}
In case of Swift 3, this is what you will need
func handleNewMessageArrived(messageContent: [AnyHashable : Any]!) {
// Handle the message
}
Note:
Here's some thoughts from CA at Parse about this:
https://www.parse.com/questions/ios-when-will-swift-for-parse-be-ready
(notice how popular that question is - hot topic). Hope it helps someone
Here's an iOS7 Parse cloud code call ...
how to do this in SWIFT ? cheers
To be clear ... can you use "callFunctionInBackground" in SWIFT, or do you have to just call to an objc class?
-(void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
int thisRow = indexPath.row;
PFUser *delFriend = [self.theFriends objectAtIndex:thisRow];
NSLog(#"you wish to delete .. %#", [delFriend fullName] );
// note, this cloud call is happily is set and forget
// there's no return either way. life's like that sometimes
[PFCloud callFunctionInBackground:#"clientRequestFriendRemove"
withParameters:#{
#"removeThisFriendId":delFriend.objectId
}
block:^(NSString *serverResult, NSError *error)
{
if (!error)
{
NSLog(#"ok, Return (string) %#", serverResult);
}
}];
[self back]; // that simple
}
Note, I've noticed this is a google landing page for trying to figure out "how the heck to do cloud code calls" (unrelated to Swift). Here is a full, complete set of example code for both iOS and Android of custom cloud code functions, in the Parse.com universe https://stackoverflow.com/a/24010828/294884 Hope it helps someone
Add an Objective-C .m file to your project. Xcode will ask about creating a Bridge Header file. Say yes.
Delete the .m file. In the bridge header file, add your import statement:
#import <Parse/Parse.h>
Another answer, which has a way better walkthrough than mine: https://stackoverflow.com/a/24005242/353988
Now you can call native Parse code in Swift, i.e.:
import UIKit
import Foundation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
Parse.setApplicationId("appid", clientKey: "clientkey")
var obj = PFObject(className:"TestObject")
obj.setObject("bar", forKey: "foo")
obj.saveInBackgroundWithBlock ({
(succeeded: Bool!, err: NSError!) -> Void in
NSLog("Hi")
})
}
}
It is not possible to call this exact function (callFunctionInBackground) because it is an obj-C function.
Please refer to this question how to call obj-c functions.
With time Parse will also introduce Swift implementation.