Implement React Native compatible location tracking native module from swift - ios

Im trying to implement location tracking module to react native from swift which uses Significant Location Tracking.
Currently I have implemented the react native bridge which transfer data from native ios modules. Also converted the appDelegate file to swift from objective-c
The Issue that I'm currently having is this module works fine in app foreground and background states, but after terminating the app no location hits get generated from the native side anymore.
Is there a way to wake up the react native app from the native side when app has been terminated, so that it continues to fetch the location or is there any issue with my current implementation?
My current implementation look like this,
React native Bridge
#import "React/RCTEventEmitter.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>
#import <React/RCTConvert.h>
#import <React/RCTBundleURLProvider.h>
AppDelegate.swift
import Foundation
import UIKit
import CoreLocation
#available(iOS 14.0, *)
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var bridge: RCTBridge!
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let jsCodeLocation: URL
UIDevice.current.isBatteryMonitoringEnabled = true
jsCodeLocation =
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot:"index")
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "AppName",
initialProperties: nil, launchOptions: launchOptions)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
Datastore.shared.appLastLaunchedDate = Date()
Datastore.shared.loadData()
UserDefaults.standard.register( defaults: ["trackingMethod": "significantLocation"])
let authStatus = LocationManager.shared.clLocationManager.authorizationStatus
if authStatus == .authorizedWhenInUse || authStatus == .authorizedAlways {
LocationManager.shared.clLocationManager.delegate = self
LocationManager.shared.startMonitoring()
}
return true
}
}
GetLocation.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "React/RCTEventEmitter.h"
#interface RCT_EXTERN_MODULE(getLocation, RCTEventEmitter)
RCT_EXTERN_METHOD(startSignificantTracking)
#end
GetLocation.swift
#objc(getLocation)
class getLocation: RCTEventEmitter, CLLocationManagerDelegate {
....
#objc
func startSignificantTracking() {
locationManager?.startMonitoringSignificantLocationChanges()
}
#objc
func locationManager(_ manager: CLLocationManager,
didUpdateLocations clLocations:
[CLLocation]) {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
let location = Location(clLocation: clLocation)
let locatioObj: [String: Any] = [
"latitude": location.clLocation.coordinate.latitude,
"longitude": location.clLocation.coordinate.longitude,
]
sendEvent(withName: "LocationUpdate", body:
["Data":locatioObj])
}
}

Why don't you use the existing solution https://github.com/Agontuk/react-native-geolocation-service#watchpositionsuccesscallback-errorcallback-options
Btw you can check how it is implemented in this library.

Related

Xcode use of undeclared type error with Drawercontroller

I have installed KYDrawercontroller in my project. it is available in project and can able to import it.any help will be appricated.thanks in advance
As you can check in KYDrawerController docs you have to use DrawerViewController and KYDrawerController, there isn't DrawerController:
import UIKit
import KYDrawerController
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let mainViewController = MainViewController()
let drawerViewController = DrawerViewController()
let drawerController = KYDrawerController()
drawerController.mainViewController = UINavigationController(
rootViewController: mainViewController
)
drawerController.drawerViewController = drawerViewController
/* Customize
drawerController.drawerDirection = .Right
drawerController.drawerWidth = 200
*/
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = drawerController
window?.makeKeyAndVisible()
return true
}

How to remove Scene Delegate from iOS Application?

I have created the iOS Project in Xcode 11.1. I need to remove scene delegate from the application.
You need to do the following steps:
Remove Scene delegate methods from App Delegate and delete the Scene delegate file.
You need to remove UIApplicationSceneManifest from Info.plist.
You also need to add var window:UIWindow? if it is not present in AppDelegate
Remove SceneDelegate.swift file
Remove Application Scene Manifest from Info.plist file
Add var window: UIWindow? to AppDelegate.swift
Replace #main with #UIApplicationMain
Remove UISceneSession Lifecycle ( functions ) in AppDelegate
It is a complete solution for empty project generated with Xcode (with storyboard)
Remove SceneDelegate.swift file
Remove Application Scene Manifest from Info.plist file
Paste this code to your AppDelegate.swift file
import UIKit
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window:UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
return true
}
}
Adding on to milzi's answer
Remove SceneDelegate.swift file
Remove Application Scene Manifest from Info.plist file
Remove UISceneSession Lifecycle function from your AppDelegate class
Add var window: UIWindow? in your AppDelegate class as a instance property
Replace #main attribute with #UIApplicationMain attribute (This saves as to manually create and assign window)
Below is how you how your AppDelegate should look like after changes:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
To add to accepted answer: you also need to write this in your AppDelegate:
self.window = UIWindow(frame: UIScreen.main.bounds)
let controller = MyRootViewController()
window?.rootViewController = controller
window?.makeKeyAndVisible()
Just in case if you are developing in Objective-C, add window property in the AppDelegate.h file like this:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
No need to initialise the window. It will work automatically.

Objective-C AppAuth OIDAuthorizationService class in Swift, and Objective-C -> Swift translation

I need to declare a variable of type OIDAuthorizationService, from the AppAuth (https://github.com/openid/AppAuth-iOS) library, in Swift while translating the following Objective-C to Swift to use an Objective-C library in a project.
Pre-translated objective-c
.h
+ #protocol OIDAuthorizationFlowSession;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
+ #property(nonatomic, strong, nullable)
id<OIDAuthorizationFlowSession> currentAuthorizationFlow;
#property (nonatomic, strong) UIWindow *window;
#end
.m
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) {
_currentAuthorizationFlow = nil;
return YES;
}
return NO;
}
So far I have the following translation:
var currentAuthorizationFlow: OIDAuthorizationFlowSession?
...
func application(_ application: UIApplication, openURL: NSURL,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if currentAuthorizationFlow!.resumeAuthorizationFlow(with: openURL as URL) {
return true
}
return false
}
Is this code correct?
Error is: Use of undeclared type: 'OIDAuthorizationFlowSession', as espected, how do I do this?
Much appreciated
I understand that OIDAuthorizationFlowSession protocol is defined in Objective-C and you're trying to use it in Swift, if that's the case then you need a Bridging-Header.h, there you can import the corresponding header for your OIDAuthorizationFlowSession.
How do you create a Bridging Header file? Well it should be created automatically when you create a new swift file, if that's not the case, take a look here: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
If the easy way doesn't work, see this one: Xcode not automatically creating bridging header?
LE: if you integrated the AppAuth-iOS as a pod, here's what's working 100%:
import UIKit
import AppAuth
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var currentAuthorizationFlow: OIDAuthorizationFlowSession?
func application(_ application: UIApplication, openURL: NSURL,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if currentAuthorizationFlow!.resumeAuthorizationFlow(with: openURL as URL) {
return true
}
return false
}
So don't forget the "import AppAuth" part.

Google Maps iOS SDK

This is my code
import UIKit
import GooglePlaces
import GoogleMaps
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
GMSPlacesClient.provideAPIKey("************************")
GMSServices.provideAPIKey("************************")
return true
}
but I'm still getting the exception
Terminating app due to uncaught exception 'GMSServicesException',
reason: 'Google Maps SDK for iOS must be initialized via [GMSServices
provideAPIKey:...] prior to use'
Is there any other cause, help me to fix it.
ViewController class is calling before appDelegate. So, the APIKey was not initiated. After finding this I initiate the viewController in appDelegate.
You must add API Key in App delegate and implement GoogleMap delegate in ViewController
import UIKit
import GoogleMaps
let googleApiKey = "<YOUR API Key HERE>"
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GMSServices.provideAPIKey(googleApiKey)
return true
}
//other methods
}
and your ViewController should be like
import UIKit
import GoogleMaps
class ViewController: UIViewController, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
#IBOutlet weak var mapView: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
PS: Make sure you don't forget these important steps.
Go to Google Developers Console and create app and API Key
Enable APIs for Google Maps SDK for iOS
Add privacy permissions in .Plist file
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Application requires user’s location information while the app is running in the foreground.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Will you allow this app to always know your location?</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Do you allow this app to know your current location?</string>

Swift - App Delegate not passing data

I'm developing an App that it's data is from a URL, here's a sample code that I'm using
AppDelegate.swift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var fromUrl: String!
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject)-> Bool {
print("Host: \(url.host!)")
self.fromUrl = url.host!
return true
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
let appDelegate = AppDelegate()
override func viewDidLoad() {
super.viewDidLoad()
print(appDelegate.fromUrl)
}
It's is logging the url.host from app delegate. But when i try to log the value of fromUrl from the ViewController.swift it's returning nil. What do you think seems to be the problem? Thanks!
When you declare let appDelegate = AppDelegate() in ViewController you are actually instantiating another instance of AppDelegate. That is not the same instance that you are using as you actual ApplicationDelegate. Try getting that reference by using:
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
print(appDelegate.fromUrl)
}

Resources