I am trying to build a plug in for a Cordova App. In this plug in I have some native code for push notifications. I have added the files needed inside the plug in and now i am trying to call a header from app's AppDelegate.m. But the import has the error "file not found". I have added the header file in the plugin's plugin.xml as: < header-file src="src/ios/xxxxx/xxxxx.h" />
Any thoughts?
AppDelegate.m is auto-generated when Cordova creates the iOS platform project so making direct modifications to it is not recommended since if you rebuild the platform project with cordova plugin rm ios && cordova platform add ios, any changes will be lost.
Plugins can work around this by using a category extension to the AppDelegate class which allows you to bundle code which extends the AppDelegate in your plugin repo without affecting the auto-generated file.
For example, you would create myplugin/src/ios/AppDelegate+MyPlugin.h:
#import "AppDelegate.h"
#interface AppDelegate (MyPlugin) <UIApplicationDelegate>
#end
Then in myplugin/src/ios/AppDelegate+MyPlugin.m you can implement your app delegate, for example:
#import "AppDelegate+MyPlugin.h"
#implementation AppDelegate (MyPlugin)
// A UIApplication delegate
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"Remote notification received");
}
...
To allow your AppDelegate category to interact with your Cordova plugin class, you can expose a public method which returns its singleton instance which the AppDelegate category can call; for example:
myplugin/src/ios/MyPlugin.h:
#import <Cordova/CDV.h>
#interface MyPlugin : CDVPlugin
// Public static method
+ (MyPlugin*) myPlugin;
// A public instance method
- (void)logMessage: (NSString*)msg;
#end
myplugin/src/ios/MyPlugin.m:
#import "MyPlugin.h"
#implementation MyPlugin
// Private static reference
static MyPlugin* myplugin;
// Public static method
+ (MyPlugin*) myplugin {
return myplugin;
}
// implement CDVPlugin delegate
- (void)pluginInitialize {
myplugin = self;
}
// A public instance method
- (void)logMessage: (NSString*)msg
{
NSLog(#"MyPlugin: %#", msg);
}
#end
Then in myplugin/src/ios/AppDelegate+MyPlugin.m you can make use of your plugin methods:
#import "AppDelegate+MyPlugin.h"
#import "MyPlugin.h"
#implementation AppDelegate (MyPlugin)
// A UIApplication delegate
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[MyPlugin.myPlugin logMessage:#"Remote notification received"];
}
...
An example of a plugin which takes this approach is cordova-plugin-firebasex.
Related
I followed this guide to setup the OneSignal background notification in My react native App: https://documentation.onesignal.com/docs/rn-android-native-module-setup-for-notification-service-extension#ios-notification-service-extension-module
Background notification is working as expected when I installed the App directly to My device through Xcode. But when I archive the build and install it from TestFlight, background notification doesn't work. Unless emitNotificationEvent event I added is not get triggered, even though the push notification is received.
When I trace the issue with Archived Build in Xcode (Using device console), noticed that _instance is null in NotificationExtensionModule.m. Anyone experienced similar issue or any idea what could be the reason ?
Note
Xcode Version: 13.4
Receiving the push notification in both instance (from test flight or direct install)
To put a new version in TestFlight, used to increase the build number with same version number.
Tried cleaning the build folder, re-installing the pods, nothing worked still
Adding the code snippet for further understanding of My issue:
AppDelegate.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate,RCTBridgeDelegate,UNUserNotificationCenterDelegate>
#property (nonatomic, strong) UIWindow *window;
#end
AppDelegate.m
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//access NotificationServiceExtensionModule emitNotificationEvent method
[NotificationServiceExtensionModule.sharedInstance emitNotificationEvent:userInfo ];
completionHandler(UIBackgroundFetchResultNoData);
}
NotificationServiceExtensionModule.h
#import <foundation/Foundation.h>
// NotificationServiceExtensionModule.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#interface NotificationServiceExtensionModule : RCTEventEmitter <RCTBridgeModule>
+ (NotificationServiceExtensionModule*) sharedInstance;
- (void)emitNotificationEvent:(NSDictionary *)userInfo;
#end
NotificationServiceExtensionModule.m
#import <Foundation/Foundation.h>
// NotificationServiceExtensionModule.m
#import "NotificationServiceExtensionModule.h"
#implementation NotificationServiceExtensionModule
static NotificationServiceExtensionModule* _instance = nil;
+(NotificationServiceExtensionModule*) sharedInstance {
// #synchronized( _instance ) {
// if( !_instance ) {
// _instance = [[NotificationServiceExtensionModule alloc] init];
// }
// }
return _instance; // this returns null when installed from TestFlight.
}
// To export a module named NotificationServiceExtensionModule
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents
{
NSLog(#"Supported EVENTS__________________________");
_instance = self;
return #[#"NotificationEvent"];
}
- (void)emitNotificationEvent:(NSDictionary *)userInfo
{
NSString *eventName = userInfo[#"custom"][#"a"];
[self sendEventWithName:#"NotificationEvent" body:#{#"notificationPayload": eventName}];
}
#end
You can use the following code to check if the app is running in the background or foreground:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//access NotificationServiceExtensionModule emitNotificationEvent method
[NotificationServiceExtensionModule.sharedInstance emitNotificationEvent:userInfo ];
if (application.applicationState == UIApplicationStateActive) {
// App was in the foreground when the notification was received
} else {
// App was in the background when the notification was received
}
completionHandler(UIBackgroundFetchResultNoData);
}
I have created a framework with a native sdk that the company I work has for push notifications. I have an Appdelegate inside this framework because there are some files that use it. I have added this framework as a plug in for a Cordova test application but as I see I can use only one of the two AppDelegates that exist now(one in framework and one in cordova app). I am trying to use only the framework's AppDelegate but to do this I must somehow use CDVAppDelegate. The problem is that the app in initialization doesn't run the correct didFinishLaunchingWithOptions function and I can't see the default first page of the cordova app. I have this files inside the plug in:
AppDelegateExtension.m
#import "xxxSDK/AppDelegate.h"
#import "xxxSDK/xxx.h"
#import "MainViewController.h"
#import "MyPlugin.h"
#implementation AppDelegate (MyPlugin)
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
#if (DEBUG == 1)
[xxx launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#else
[xxx launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#endif
[[xxx sharedService].pushManager registerForRemoteNotifications];
[[xxx sharedService].pushManager resetBadge];
MyPlugin.myPlugin.viewController = [[MainViewController alloc] init];
[MyPlugin.myPlugin application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
MyPlugin.h
#import <Cordova/CDVAppDelegate.h>
#interface MyPlugin : CDVAppDelegate
// Public static method
+ (CDVAppDelegate*) myPlugin;
#end
MyPlugin.m:
#import <Foundation/Foundation.h>
#import "MyPlugin.h"
#import "MainViewController.h"
#implementation MyPlugin
// Private static reference
static CDVAppDelegate* myPlugin;
// Public static method
+ (CDVAppDelegate*) myPlugin {
return myPlugin;
}
#end
Inside the framework the AppDelegate.h is this:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#end
I have tried declare MyPlugin as a CDVPlugin too but the problem didnt get solved. Any thoughts?
Because AppDelegateExtension.m is a category of the AppDelegate class it extends the original class rather than implementing a subclass, so any methods you implement in your category which already exist in the main class (i.e. AppDelegate.m auto-generated by Cordova) will be replaced.
So in this case your didFinishLaunchingWithOptions in AppDelegateExtension.m is replacing didFinishLaunchingWithOptions in AppDelegate.m.
TL;DR: You need to swizzle the method to avoid destroying the original method:
#implementation AppDelegate (MyPlugin)
+ (void)load {
Method original = class_getInstanceMethod(self, #selector(application:didFinishLaunchingWithOptions:));
Method swizzled = class_getInstanceMethod(self, #selector(application:swizzledDidFinishLaunchingWithOptions:));
method_exchangeImplementations(original, swizzled);
}
- (BOOL)application:(UIApplication*)application swizzledDidFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
{
#if (DEBUG == 1)
[xxx launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#else
[xxx launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#endif
[[xxx sharedService].pushManager registerForRemoteNotifications];
[[xxx sharedService].pushManager resetBadge];
MyPlugin.myPlugin.viewController = [[MainViewController alloc] init];
[MyPlugin.myPlugin application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
You can see an example of this in cordova-plugin-firebasex.
Ok, i did this:
#implementation AppDelegate (MyPlugin)
+ (void)load {
Method original = class_getInstanceMethod(self, #selector(application:didFinishLaunchingWithOptions:));
Method swizzled = class_getInstanceMethod(self, #selector(application:swizzledDidFinishLaunchingWithOptions:));
method_exchangeImplementations(original, swizzled);
}
- (BOOL)application:(UIApplication*)application swizzledDidFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
#if (DEBUG == 1)
[Warply launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#else
[Warply launchWithAppUUID:#"1256fbeb638b4426a98e4bacbc5f56f5" launchOptions:launchOptions];
#endif
[[Warply sharedService].pushManager registerForRemoteNotifications];
[[Warply sharedService].pushManager resetBadge];
CDVAppDelegate *appDelegate = [[CDVAppDelegate alloc]init];
[appDelegate application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
CDVAppdelegate's didFinishLaunchingWithOptions is running but I can not see in my device index.html view. My sdk runs properly but the cordova app is not running at all.
I assume that the thing is that AppDelegate : UIResponder can not support what I want to do and I am starting to believe that what I want can not be implemented.
I need to add the following code to my main AppDelegate, I have 2 files called AppDelegate, AppDelegate.m, AppDelegate.h, which one do I need to edit?
Neither file has #import they both have #import
#import UIKit;
#import Firebase;
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[FIRApp configure];
return YES;
}
#import is available since iOS 7 and is an improvement on #import (see for example this blogpost for more info). You should be able to use #import Firebase;, but otherwise it'd be #import <Firebase/Firebase.h>
Is there a way i can execute the
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
method while the app is running from a different ViewController?
Maintain a separate method in your app delegate for setting nav bar appearance.
In your AppDelegate.h file, declare the same method.
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
-(void)setNavBarAppearance;
#end
In the appDelegate.m file, write the functionality for that method
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setNavBarAppearance];
}
-(void)setNavBarAppearance {
//Do what is required here.
}
#end
Then, wherever you need to call the same method:
#import "AppDelegate.h"
AppDelegate* appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate setNavBarAppearance];
You should not call the delegate methods by your own, You can use NSNotificationCenter
Write the code you want to execute in a different method (someThingInterestingHappened) in our case.
Register the class to notification by calling addObserver on defaultNotificationCenter , i.e. [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(someThingInterestingHappened:) name:#"desiredEventHappend" object:nil];
Post the notification from any class [[NSNotificationCenter defaultCenter] postNotificationName:#"desiredEventHappend" object:nil];
I'm trying to test my core data scheme. However, it seems I am unable to create the context because it says No visible #interface for 'MyAppDelegate' declares the selector 'managedObjectContext'.
In online tutorials this method seems to be auto-generated when we create the app. However, in my case it doesn't exist.
This is MyAppDelegate:
Header
#import <UIKit/UIKit.h>
#interface MyAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
.m file
#import "MyAppDelegate.h"
#implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSManagedObjectContext *context = [self managedObjectContext];
// Override point for customization after application launch.
return YES;
}
How should I fix this in Xcode 5 with iOS 7?
I think the best way for you is to create a Master-Detail Application with Xcode 5 and don't forget to check Use Core Data :
With that, you will have an AppDelegate.h and an AppDelegate.m configured with a managedObjectContext.
You will have a project configured correctly with Core Data and a .xcdatamodeld to use easily your SQLite database.