I have a problem with Sygic maps framework, I tried to show the map using this code
in the AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
SYContext.initWithAppKey("mhives.sdk.trial", appSecret: "2nxScQJ0s/J6FsYJ67dlQ+MZLjLzOKc0s96l0t4YyLv2IH8b31b5vWgkzbfgJZ8FYEKQtxpLFGlwyfqEQ64MSQ==") { (initResult) in
if (initResult == .success) {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController:ViewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
// Set that ViewController as the rootViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
initialViewController.sdkDidStart()
} else {
print("KO")
}
}
return true
}
the ViewController.swift
class ViewController: UIViewController,SYMapViewDelegate,SYRoutingDelegate {
let mapView = SYMapView()
let routing = SYRouting()
override func viewDidLoad() {
super.viewDidLoad()
}
func mapView(_ mapView: SYMapView, didFinishInitialization success: Bool) {
let tiltFor2D: SYAngle = 0
mapView.tilt = tiltFor2D
self.mapView.zoom = 10
self.mapView.rotation = 180
self.mapView.geoCenter = SYGeoCoordinate(latitude: 48.147, longitude: 17.101878)!
self.mapView.frame = self.view.bounds
view.addSubview(self.mapView)
}
func computeRoute(from fromCoordinate: SYGeoCoordinate, to toCoordinate: SYGeoCoordinate) {
// Create an instance of SYRouting
// Make self a delegate for SYRouting to receive and handle SYRoutingDelegate responses
// Create SYWaypoint from coordinate. Set correct SYWaypointType for start and finish.
let startWaypoint = SYWaypoint(position: fromCoordinate, type: .start, name: nil)
let endWaypoint = SYWaypoint(position: toCoordinate, type: .end, name: nil)
// Optionally: create an instance of SYRoutingOptions for configuring computing of a route
let routingOptions = SYRoutingOptions()
routingOptions.transportMode = .pedestrian // For other options see SYTransportMode
routingOptions.routingType = .economic// For other options see SYRoutingType
// Start computing route
self.routing.computeRoute(startWaypoint, to: endWaypoint, via: nil, with: routingOptions)
}
func routing(_ routing: SYRouting, computingFailedWithError error: SYRoutingError) {
print(error.rawValue)
}
func routing(_ routing: SYRouting, didComputePrimaryRoute route: SYRoute?) {
SYNavigation.shared().start(with: route)
// You might want to put it also on the map
let mapRoute = SYMapRoute(route: route!, type: .primary)
mapView.add(mapRoute)
}
Now I try to show simply the Sygic maps but I didn't get it and I receive this message in the debugger "Sygic: W 18/08/19 17:04:51 No SSO session, unable to send http request!"
func sdkDidStart(){
mapView.delegate = self
routing.delegate = self
self.mapView.frame = self.view.bounds
self.view.addSubview(self.mapView)
let start = SYGeoCoordinate(latitude: 37.276758, longitude: 9.864160900000002)
let end = SYGeoCoordinate(latitude: 37.25408, longitude: 9.906733)
//computeRoute(from: start!, to: end!)
}
Any help please
First of all do you have valid key and secret? If not you can request one here: https://www.sygic.com/business/request-sygic-mobile-sdk-api-key?product=mobileSdk
I understand you are not using real one here in example, but at least the error seems like the one you are using isn't valid.
The other thing is, when do you initialize your SYMapView? If you load ViewController as initial view controller using storyboard for example, then it will try to load map view instance and all of its underlying components before the SDK is fully loaded. Be careful, you should only work with SDK after it is successfully initialized.
So check your API keys and try to initialise that view controller in SYContext.initWithAppKey() completion handler.
Related
I want to pop current view controller on some condition from appDelegate but I don't know how to do so, if any idea please help me out...................................................................................
import UIKit
import IQKeyboardManagerSwift
let kSharedAppDelegate = UIApplication.shared.delegate as? AppDelegate
#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.
self.window = UIWindow(frame: UIScreen.main.bounds)
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
IQKeyboardManager.shared.enableAutoToolbar = false
//IQKeyboardManager.shared.toolbarTintColor = .white
//IQKeyboardManager.shared.toolbarBarTintColor = ColorSet.appTheamColor
kSharedAppDelegate?.moveToSplashVC()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
}
//MARK:- Show No Internet connection VC
func showNoInterNetVC() {
guard let controller = UIStoryboard(name: Storyboards.kNoInternet, bundle: nil).instantiateViewController(withIdentifier: Identifiers.kNoInternetPopUpViewController) as? NoInternetPopUpViewController else {return}
controller.modalPresentationStyle = .overFullScreen
controller.modalTransitionStyle = .crossDissolve
kSharedAppDelegate?.window?.rootViewController?.present(controller, animated: true, completion: nil)
//window.present(controller , animated: true)
}
}
I think pop is the wrong terminology here unless you are using a navigation controller.
If you want to dismiss the currently presented viewController you could check the rootViewController of the applications Window like this.
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
I also just noticed that you may not need to access the application singleton via the shared property, as applicationDidBecomeActive(_ application: UIApplication) is passing you the Application already - that line would become:
guard let window = application.windows.first else {
There is another way to do this, in newer iOS versions you directly have access to topViewController() so you access it like UIApplication.topViewController() the only downside of it is that you need yo wrap it into an if let statement to check if it is not null. FYI, It won’t be null in most cases if you have let your didFinishLaunching() method run at least once in your app delegate. So that it can stack a view controller to be a top view controller. This won’t be a problem for you since all of the other methods will fail as well if this is the case.
Todo a pop view controller now all you need to do it use top view controller and perform pop on its navigation view controller, or you can dismiss it in case there is no navigation view controller.
class TopViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Code Block 1
let controller = getTopController()
print(controller)// Prints out MyTestProject.TopViewController
//Code Block 2
let controller2 = getRootController()
print(controller2)//Prints out nil , because keywindow is also nil upto this point.
//Code Block 3
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
let controller2 = self.getRootController()
print(controller2)// Prints out MyTestProject.TopViewController
}
}
func getTopController() -> UIViewController? {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sceneDelegate = windowScene.delegate as? SceneDelegate else {
return nil
}
return sceneDelegate.window?.rootViewController
}
func getRootController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let topController = keyWindow?.rootViewController
return topController
}
}
Since iOS 13 there is two approach to get current active / top view controller of the app.
here:
getTopController() and getRootController() shows both of the approaches.
As commented in codes besides print() results are different though.
In Code Block 2:
getRootController can't find the window yet so it prints out nil. Why is this happening?
Also, which is the full proof method of getting reference to top controller in iOS 13, I am confused now?
The problem is that when your view controller viewDidLoad window.makeKey() has not been called yet.
A possible workaround is to get the first window in the windows array if a key window is not available.
func getRootController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ?? UIApplication.shared.windows.first
let topController = keyWindow?.rootViewController
return topController
}
Please note that this will solve your problem but you should postpone any operation that involve using a key window until it is such.
According to the documentation of UIView, the window property is nil if the view has not yet been added to a window which is the case when viewDidLoad is called.
Try to access that in viewDidAppear
override func viewDidAppear(_ animated: Bool) {
let controller2 = self.view.window.rootViewController
}
I'm experimenting a weird (in my opinion) change in behaviour of a function depending on how I get to the VC that contains it.
When user taps on a remote notification action the app goes from background to foreground,go to NewMapViewController, load up a route, display it as a MKPolyline and set the region based on the MKPolyline MapRect. Now the problem I'm facing is that setRegion() doesn't work depending on how I get to NewMapViewController. If in the action I instantiate the main UINavigationController and get to NewMapController via a segue than setRegion in displayRoute() works as expected but I see the loginVC before showing it. So not to show loginVC I instead instantiate NewMapViewController directly, but doing so setRegion in displayRoute() does not zoom on the MKPolyline anymore. Why can this be happening?
At first I thought that configureMap( centers map on user location ) was being called but no, all the prints show that when NewMapViewController gets loaded with that counter at 1, it never gets called.
I tried using setVisibleMapRect() instead of setRegion() but it made no difference. Is it a known bug? Is there any other way to get to newMapViewController that I should try? As always many thanks for your time and help.
Update:
After a few tries I decided to call checkAlerts() directly from the action body instead that from viewWillApper() counter check. That led to a nil error when adding the MKPolyline in displayRoute(). Searching a bit I found out that it is a thread problem, so by adding it in a DispatchQueue.main.async queue it all worked again as expected.
It works whether I call checkAlerts() from the action body or from viewWillApper() counter check.
It seems that when I instantiate the UINavigationController and go to NewMApControllervia a segue it has the time to load the route and calculate the polyline, but when instantiating directly NewMapController does not. I'd like to understand it a bit better, does this make sense to you ?
This is the action:
case Id.checkActionIdentifier:
print("Check tapped")
let userInfo = response.notification.request.content.userInfo
if let routeToCheck = userInfo[NSLocalizedString("Route", comment: "")] as? String {
RoutesArray.routeName = routeToCheck
// print("rotta is: \(routeToCheck)")
}
// let content = response.notification.request.content
// print(" Body \(content.body)")
// print(" Title \(content.title)")
NewMapViewController.routeCheckCounter = 1
// Goes to NewMapViewController but showing the loginVc first
// let storyboard = UIStoryboard(name: "Main", bundle: nil)
// let initialViewController : UINavigationController = storyboard.instantiateViewController(withIdentifier: "MainNavigationController") as! UINavigationController
// self.window?.rootViewController = initialViewController
// self.window?.makeKeyAndVisible()
// let vc = initialViewController.viewControllers[0]
// vc.performSegue(withIdentifier: "alertToMapSegue", sender: nil)
// setRegion in displayRoute() not working ???
if let navigationVC = self.window?.rootViewController as? UINavigationController {
let main = UIStoryboard(name: "Main", bundle: nil)
let newMapVC = main.instantiateViewController(withIdentifier: "NewMapViewController") as! NewMapViewController
navigationVC.pushViewController(newMapVC, animated: true)
}
and this is the function that displays the MKPolyline:
func displayRoute() {
print("displayRoute called")
var coordinateArray: [CLLocationCoordinate2D] = []
for point in MapArray.actualRouteInUseCoordinatesArray {
let location = CLLocationCoordinate2D(latitude: point.coordinate.latitude, longitude: point.coordinate.longitude)
coordinateArray.append(location)
}
// tracked CLLocations route
routePolyline = MKGeodesicPolyline(coordinates: coordinateArray, count: coordinateArray.count)
mapView.add(routePolyline, level: MKOverlayLevel.aboveRoads)
let startRouteAnnotation: RouteAnnotation = RouteAnnotation(title: "Route Start", coordinate: MapArray.actualRouteInUseCoordinatesArray.first!.coordinate)
self.mapView.addAnnotation(startRouteAnnotation)
let endRouteAnnotation = RouteAnnotation(title: "Route End", coordinate: MapArray.actualRouteInUseCoordinatesArray.last!.coordinate)
self.mapView.addAnnotation(endRouteAnnotation)
print("MapArray.actualRouteInUseCoordinatesArray.count is: \(MapArray.actualRouteInUseCoordinatesArray.count)")
// full display
let region = MKCoordinateRegionForMapRect(routePolyline!.boundingMapRect)
mapView.setRegion(region, animated: true)
// padded in
// self.mapView.setRegion(MKCoordinateRegionForMapRect(padMapRect(rect: routePolyline.boundingMapRect)), animated: true)
}
this is how I get to call the function at viewWillAppear() :
(checkAlerts() is than calling displayRoute().
// if loading vc from an scheduled alert check notification
print(" CheckCounter is: \(NewMapViewController.routeCheckCounter ?? 0)")
if NewMapViewController.routeCheckCounter ?? 0 > 0 {
print("checkAlerts called in viewDidLoad")
// load the route and call displayRoute()
checkAlerts()
} else {
// just center map on user location
configureMap()
return
}
After the tries described in the update part of my question here is the modified function that works just as expected:
func displayRoute() {
print("displayRoute called")
var coordinateArray: [CLLocationCoordinate2D] = []
for point in MapArray.actualRouteInUseCoordinatesArray {
let location = CLLocationCoordinate2D(latitude: point.coordinate.latitude, longitude: point.coordinate.longitude)
coordinateArray.append(location)
}
DispatchQueue.main.async {
// tracked CLLocations route
self.routePolyline = MKGeodesicPolyline(coordinates: coordinateArray, count: coordinateArray.count)
self.mapView.add(self.routePolyline, level: MKOverlayLevel.aboveRoads)
let startRouteAnnotation: RouteAnnotation = RouteAnnotation(title: "Route Start", coordinate: MapArray.actualRouteInUseCoordinatesArray.first!.coordinate)
self.mapView.addAnnotation(startRouteAnnotation)
let endRouteAnnotation = RouteAnnotation(title: "Route End", coordinate: MapArray.actualRouteInUseCoordinatesArray.last!.coordinate)
self.mapView.addAnnotation(endRouteAnnotation)
print("MapArray.actualRouteInUseCoordinatesArray.count is: \(MapArray.actualRouteInUseCoordinatesArray.count)")
// self.mapView.setRegion(MKCoordinateRegionForMapRect(padMapRect(rect: routePolyline.boundingMapRect)), animated: true)
self.mapView.setVisibleMapRect(self.padMapRect(rect: self.routePolyline.boundingMapRect), animated: true)
}
}
I'm using a cocoapod named (p2/OAuth2) in order to log in an account with GitHub. I'm just playing because I want to know how OAuth2 works.
The following is what I have so far in my view controller (UIViewController).
import UIKit
import p2_OAuth2
class ViewController: UIViewController {
// MARK: - Variables
let oauth2 = OAuth2CodeGrant(settings: [
"client_id": "xxxxxx",
"client_secret": "xxxxxxxx",
"authorize_uri": "https://github.com/login/oauth/authorize",
"token_uri": "https://github.com/login/oauth/access_token", // code grant only
"redirect_uris": ["myapp://oauth/callback"], // register your own "myapp" scheme in Info.plist
"scope": "user repo:status",
"secret_in_body": true, // Github needs this
"keychain": false, // if you DON'T want keychain integration
] as OAuth2JSON)
// MARK: - IBOutlet
// MARK: - IBAction
#IBAction func loginTapped(_ sender: UIBarButtonItem) {
oauth2.logger = OAuth2DebugLogger(.trace)
oauth2.authorize() { authParameters, error in
if let params = authParameters {
print("Authorized! Access token is \(self.oauth2.accessToken ?? "")")
print("Authorized! Additional parameters: \(params)")
}
else {
print("Authorization was cancelled or went wrong: \(String(describing: error))")
}
}
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
What I would like to know is how to get back to the view controller after I successfully log in my account with GitHub. So far, I will be redirected to the URL that I have registered at GitHub's developer website. I suppose I need to register a callback URL scheme under Info. This whole thing is totally new. So I'm not sure how to do it. Thanks.
UPDATE
I have added a set of URL types through info.plist. A URL scheme is set to myapp. And the following is what I have in AppDelegate.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "oauth" {
//oauth2.handleRedirectURL(url)
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeView") as! UINavigationController
let viewController = controller.topViewController as! ViewController
window?.rootViewController = viewController
}
window?.makeKeyAndVisible()
return true
}
But the app never calls the above.
I have learnt something new today.
At GitHub's developer website, I have set the call back URL as myapp://oauth/callback. (See below.)
I have created a set of URL schemes through Info.plist. An identifier is made with my reverse domain + a unique name. A URL Schemes item is myapp. (See below.)
Finally, I have added the following line of code to AppDelegate.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "oauth" {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeView") as! UINavigationController
let viewController = controller.topViewController as! ViewController
window?.rootViewController = viewController
}
window?.makeKeyAndVisible()
return true
}
If I log in, I get prompted as follows.
When I tap Open, I'm redirected to my view controller.
I'm setting my window's root view controller as the login controller right off the bat, and the login controller checks user authentication from Firebase. If the user is logged in, the controller changes the root view controller to be the feed controller. Otherwise, the login controller proceeds as itself.
class LoginController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild(uid) {
AppDelegate.launchApplication()
}
else {
self.setUpUI()
}
})
}
else {
self.setUpUI()
}
}
...
}
where launchApplication is
class func launchApplication() {
guard let window = UIApplication.shared.keyWindow else { return }
window.rootViewController = UINavigationController(rootViewController: FeedController())
}
In addition to if let uid = Auth.auth().currentUser?.uid, I'm checking whether the uid (if it isn't nil) exists in my database, because I have had the situation where a deleted user still wasn't nil.
The problem is that after the launch screen finishes, there is a moment when the login controller, though blank, is visible. Sometimes this moment lasts a few seconds. How can I check authentication such that the login controller isn't visible at all—so that the app decides how to proceed immediately after the launch screen disappears? Thanks.
1) use this following code in case if you want to set a rootController from app delegate itself . use a check if your currentUser.uid is not nil and matched with values in database then perform following code in DidFinishLaunchingWithOptions of Appdelegate . Used by me
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exist() {
let storyBoard = UIStoryboard.init(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "feedController") as! FeedController
self.window?.rootViewController=vc
self.window?.makeKeyAndVisible()
}
else {
//Present your loginController here
}
})
}
return true
}
2) Method: when you had initialised your logincontroller instead of calling a function from Appdelegate or code in LaunchApplication. make a function in login class and write the following code when required parameters are matched
var transition: UIViewAnimationOptions = .transitionFlipFromLeft
let rootviewcontroller: UIWindow = ((UIApplication.shared.delegate?.window)!)!
rootviewcontroller.rootViewController = self.storyboard?.instantiateViewController(withIdentifier: "rootnav")//rootnav is Storyboard id of my naviagtionController attached to the DestinationController [FeedController]
let mainwindow = (UIApplication.shared.delegate?.window!)!
mainwindow.backgroundColor = UIColor(hue: 0.6477, saturation: 0.6314, brightness: 0.6077, alpha: 0.8)
UIView.transition(with: mainwindow, duration: 0.55001, options: transition, animations: { () -> Void in
}) { (finished) -> Void in
}
As a quick fix, you could try checking just the desired "users" child node (and not the complete branch). For instance:
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() { ...
This could reduce your delay considerably if your database contains many users.
If that isn't enough, you might consider moving this logic to you AppDelegate class as well and show your LoginController from there (and maybe holding off your launch screen a little longer until you find out if an user is available).
Instead of changing any code, I simply set the background of the login controller to match the launch screen:
class LoginController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// here
let background_image_view = UIImageView(image: #imageLiteral(resourceName: "launch_screen_background"))
background_image_view.frame = self.view.frame
self.view.addSubview(background_image_view)
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild(uid) {
AppDelegate.launchApplication()
}
else {
self.setUpUI()
}
})
}
else {
self.setUpUI()
}
}
...
}
There is no noticeable transition from the launch screen to the login controller.