SKMaps and ViaPoints/WayPoints - ios

I have questions regarding skmap sdk functionality.
After creating a route and starting the navigation process, is there anyway of capturing the waypoint/viapoint that you are currently directed towards? After calculating a route, it seems routeInformation.viaPointsOnRoute is empty. Does calculateRoute(route) automatically generate a list of viaPoints? In the SKNavigationDelegate Protocol Reference I see the following event functions :
– routingService:didEnterViaPointArea:
– routingService:didReachViaPointWithIndex:
– routingService:didExitViaPointArea:
However, these methods never seem to be called. Not sure if they are only called if ViaPoints are programmatically added.
Also when the routeService fires the currentAdvice and nextAdvice methods, the SKRouteAvice location appear to be empty as well.
Is there anyway someone could provide me with a simple example of how to create and capture the current waypoint position and the next waypoint position while in navigation? Essentially, I am trying to calculate the direction from the current location to the next waypoint.
Below is rough example of my route and navigation initiation code:
//Created Route
let route: SKRouteSettings = SKRouteSettings()
route.routeMode = SKRouteMode.Pedestrian
route.startCoordinate = coordinateCurrent!
route.destinationCoordinate = coordinateDestination!
route.shouldBeRendered = true
SKRoutingService.sharedInstance().calculateRoute(r oute)
//Start Navigation
let navSettings: SKNavigationSettings = SKNavigationSettings()
navSettings.navigationType = SKNavigationType.Simulation
navSettings.distanceFormat = SKDistanceFormat.MilesFeet
navSettings.viaPointNotificationDistance=1
self.mapView!.settings.displayMode = SKMapDisplayMode.Mode3D
navSettings.transportMode = SKTransportMode.Pedestrian
SKRoutingService.sharedInstance().startNavigationW ithSettings(navSettings)
//Start Postioner Service
SKPositionerService.sharedInstance().delegate = self
SKPositionerService.sharedInstance().startLocation Update()
routeInfo.viaPointsOnRoute
func routingService(routingService: SKRoutingService!, didChangeNextAdvice nextAdvice: SKRouteAdvice, isLastAdvice:Bool)
{
log(String(nextAdvice.location))
}
func routingService(routingService: SKRoutingService!, didChangeCurrentAdvice currentAdvice: SKRouteAdvice, isLastAdvice:Bool)
{
log(String(currentAdvice.location))
}
func routingService(routingService: SKRoutingService!,
didUpdateViaPointsInformation viaPoints: [AnyObject]!)
{
log("VIA INFO: " + String(viaPoints))
}
func routingService(routingService: SKRoutingService!,
didEnterViaPointArea index: Int32)
{
log("ENTER VIA: " + String(index))
}
func routingService(routingService: SKRoutingService!,
didExitViaPointArea index: Int32)
{
log("EXIT VIA: " + String(index))
}
Thank you for your time!

I think that first we have to clarify the terminology.
viaPoint or waypoint (the skobbler/telenav SDK uses the viaPoint terminology) would be a point on the route that is explicitly declared as a "viaPoint". These points are added at route calculation time (by passing an array of viaPoints in the route settings) or during navigation with the addViaPoint API.
These viaPoints will be monitored by the SDK and you'll receive the didEnterViaPointArea, didReachViaPointWithIndex and didExitViaPointArea callbacks.
I believe that you're using the term waypoint by describing a "junction" or "point requiring an advice" along the route - these would mostly correspond to the "route coordinates" provided by the SDK (see the routeCoordinationsForRouteWithID API). These route coordinates are not explicitly surfaced by the SDK - you don't get any explicit information about these points but only indirect notifications in the form of advices ("In 200 meters turn right onto Main Street").
What the turn by turn engine provides you is exactly the "direction" between 2 such coordinates (in the form of an advice: "carry straight on", "turn right", "turn left"). See the documentation regarding how the advices are created for more details (here and here)
Maybe the skobbler/Telenav team can provide you better advice if you could describe your end goal (what use case you are trying to achieve).

Related

Razor pay is not redirecting to checkout page, why?

I am using,
*Xcode - 10.2
*Swift language version - swift 5
*RazorPay framework version - 1.1.1 (pod 'razorpay-pod', '1.1.1')
My problem is when I am calling this,
razorpay.open(options, displayController: self)
It gives me an unexpected error (code - 1) with
/Users/travis/build/razorpay/razorpay-ios/RazorpayIOS/CheckoutOtpelf/Classes/RazorpayCheckoutVC.swift deinitialized
Solutions I tried were,
Github community says to hide navigation bar before calling open function(https://github.com/razorpay/razorpay-pod/issues/42).
Used multiple framework version of razor pay
Tried with swift 4.2 also
I have cleaned my project, deleted derived data and rebuilt it.
Here's my code
import Razorpay
class controller: RazorpayPaymentCompletionProtocol{
private var razorpay: Razorpay!
override func viewDidLoad() {
super.viewDidLoad()
razorpay = Razorpay.initWithKey("test_key", andDelegate: self)
}
func openRazorPay(){
let options = [
"amount" : "12.00"
]
self.navigationController?.isNavigationBarHidden = true
razorpay.open(options, displayController: self)
}
func onPaymentSuccess(_ payment_id: String) {
print("success")
}
func onPaymentError(_ code: Int32, description str: String) {
print("Failure")
}
}
This framework supports Android, but not for iOS. I want to get the payment flow. If anyone has any solution, share with me.
As I face the same issue......
What I did differently is I take a static amount value 100 and pass the other data as it is.....and I make payment process...it works...open the payment window.
If your amount value less than 100 then it happens...as per doc, the amount we are assigning in the param consider it as a paisa....Try with this one and you will get expected result..
Here what the amount you are passing, make a multiply with 100 e.g 1 x 100. The internal processing will be count it as INR 1, but their calculation is consider it as paisa. So you passed value 100 means 100 paisa and in payment window it will show INR 1.
I was facing the same issue And All of the parameters which i was sending to the options was right.
But the issue was when we try to open RazorPay screen for payment was getting the issue of the RazorpayCheckoutVC.swift deinitialized
In my case my parent viewController was presented so i just changed the navigation code of present to the push and then it worked.
So as per my understanding it should be as
NavigationController -> Push ViewController -> RazorPaycheckoutVC.

SVProgressHUD code executes out of order

I'm building a bus predictions app using the NextBus API that will help users get prediction times and bus information. I've implemented a function that takes the user's current location and a chosen address and returns a list of 10 bus routes that minimize travel distance and time.
Here's the #IBAction that triggers the aforementioned function:
#IBAction func findAWayPressed(_ sender: UIButton) {
// Hide confirm button.
confirmButton.isHidden = true
// Setup loading HUD.
let blue = UIColor(red: 153/255, green: 186/255, blue: 221/255, alpha: 1.0)
SVProgressHUD.setBackgroundColor(blue)
SVProgressHUD.setStatus("Finding a way for you...")
SVProgressHUD.setBorderColor(UIColor.black)
SVProgressHUD.show()
// Finds a list of ten bus routes that minimizes the distance from the user and their destination.
WayFinder.shared.findAWay(startCoordinate: origin!, endCoordinate: destination!)
SVProgressHUD.dismiss()
}
The problem is that confirmButton.isHidden = true and the SVProgressHUD lines only seem to do anything after the WayFinder.shared.findAWay() executes. The HUD displays for a brief moment before being immediately dismissed by SVProgressHUD.dismiss().
Here's the findAWay function:
func findAWay(startCoordinate: CLLocationCoordinate2D, endCoordinate: CLLocationCoordinate2D) {
// Get list of bus routes from NextBus API.
getRoutes()
guard !self.routes.isEmpty else {return}
// Initialize the the lists of destination and origin stops.
closestDestinations = DistanceData(shortestDistance: 1000000, stops: [])
closestOrigins = DistanceData(shortestDistance: 1000000, stops: [])
// Fetch route info for every route in NextBus API.
var routeConfigsDownloaded: Int = 0
for route in routes {
// Counter is always one whether the request fails
// or succeeds to prevent app crash.
getRouteInfo(route: route) { (counter) in
routeConfigsDownloaded += counter
}
}
while routeConfigsDownloaded != routes.count {}
// Iterate through every stop and retrieve a list
// of 10 possible destination stops sorted by distance.
getClosestDestinations(endCoordinate: endCoordinate)
// Use destination stop routes to find stops near
// user's current location that end at destination stops.
getOriginStops(startCoordinate: startCoordinate)
// Sort routes by adding their orign distance and destination
// distance and sorting by total distance.
getFoundWays()
}
private func getRouteInfo(route: Route, completion: #escaping (Int) -> Void) {
APIWrapper.routeFetcher.fetchRouteInfo(routeTag: route.tag) { (config) in
if let config = config {
self.routeConfigs[route.tag] = config
} else {
print("Error retrieving route config for Route \(route.tag).")
}
completion(1)
}
}
Why would the code in #IBAction not execute in order? How could the hud not show on screen before findAWay was called? Any ideas?
So, you're going to want to do some reading about the "main thread" and how it works. Maybe UNDERSTANDING THE IOS MAIN THREAD
Basically, you're asking the system to show the HUD, then performing, what I assume is a long running and blocking operation, and then dismiss the HUD all within the main thread.
It's impossible for the system to show the HUD until the method exists, as it will part of the next cycle (paint/layout/other important stuff). In cases like this, I would lean towards some kind of "promise" API, like PromiseKit or Hydra as it will greatly simply the thread hoping.
The basic intent is - While on the main thread, present the HUD, using a background thread, execute the query, when it's complete, dismiss the HUD, but do so on the main thread.
Which might look something like this..
SVProgressHUD.show()
DispatchQueue.global(qos: .userInitiated).async {
WayFinder.shared.findAWay(startCoordinate: origin!, endCoordinate: destination!)
DispatchQueue.main.async {
SVProgressHUD.dismiss()
}
}
Now remember, never modify the UI from outside the main thread context, if the OS detects this, it will crash your App!
I might also consider using a DispatchSemaphore instead of a "wild running" while-loop, so instead of..
// Fetch route info for every route in NextBus API.
var routeConfigsDownloaded: Int = 0
for route in routes {
// Counter is always one whether the request fails
// or succeeds to prevent app crash.
getRouteInfo(route: route) { (counter) in
routeConfigsDownloaded += counter
}
}
while routeConfigsDownloaded != routes.count {}
You might use something like...
let semaphore = DispatchSemaphore(value: routes.count)
// Fetch route info for every route in NextBus API.
var routeConfigsDownloaded: Int = 0
for route in routes {
// Counter is always one whether the request fails
// or succeeds to prevent app crash.
getRouteInfo(route: route) { (counter) in
semaphore.signal()
}
}
semaphore.wait()
which will do the same thing, but more efficiently

Track the time it takes a user to navigate through an iOS app's UI

I want to measure how long (in seconds) it takes users to do certain things in my app. Some examples are logging in, pressing a button on a certain page, etc.
I am using an NSTimer for that. I am starting it in the viewDidLoad of a specific page, and stopping it at the point that I want to measure.
I also want to measure cumulative time for certain things. I would like to start the timer on the log-in screen, and then continue the timer until the user gets to the next view controller and clicks on a certain button.
I'm not sure how to do this. Should create a global variable in my app delegate? Or is there some other better way?
No need for an NSTimer, you just need to record the start times and compare them to the stop times. Try using a little helper class such as:
class MyTimer {
static let shared = MyTimer()
var startTimes = [String : Date]()
func start(withKey key: String) {
startTimes[key] = Date()
}
func measure(key: String) -> TimeInterval? {
if let start = startTimes[key] {
return Date().timeIntervalSince(start)
}
return nil
}
}
To use this, just call start(withKey:) right before you start a long-running task.
MyTimer.shared.start(withKey: "login")
Do something that takes a while and then call measure(key:) when you're done. Because MyTimer is a singleton, it can be called from anywhere in your code.
if let interval = MyTimer.shared.measure("login") {
print("Logging in time: \(interval)")
}
If you're using multiple threads, you may to to add some thread safety to this, but it should work as is in simple scenarios.

GeoFire swift 3 query not returning results in the order expected from GeoFire/FireBase

Here is the relevant function in my ViewController:
#IBAction func findPeople(_ sender: Any) {
let center = CLLocation(latitude: myLocation.latitude, longitude: myLocation.longitude)
let radiusQuery = geoFireUserLocations!.query(at: center, withRadius: 0.05)
print("Start Looking for People")
_ = radiusQuery!.observe(.keyEntered, with: { (key: String?, location: CLLocation?) in
print(" ALERT: Found Someone Close : ",key!)
})
_ = radiusQuery?.observeReady({
print(" All initial data has been loaded and events have been fired!")
})
print("Done Looking for People")
//TODO: Based on the GeoFire result do something smart
}
The operation works, but the response comes in an unexpected order. I thought that the .observeReady call would return the initial set of results, but it does not return anything right away, and execution continues on to the line that prints "Done Looking for People".
Is there any way to have my function block until it gets an initial set of results?
Here's the output that I get:
Start Looking for People
Done Looking for People
ALERT: Found Someone Close : oSf00ex6SyMAwpF2NRxymyxxx123
All initial data has been loaded and events have been fired!
I was expecting:
Start Looking for People
ALERT: Found Someone Close : oSf00ex6SyMAwpF2NRxymyxxx123
All initial data has been loaded and events have been fired!
Done Looking for People
How can I create a function that blocks until it gets an initial result from GeoFire?
I'd like to send the request, get a result, then take the appropriate action based on the result. The way the app is currently behaving there does not seem to be a way to have it wait for a result.
Can anyone offer some insight and/or suggestion about how to make this work the way I'm describing? Is there some sort of blocking call that I could call or a synchronization method that I could be using to ensure that I get a result before deciding the next action?
Cheers!

Being a good citizen displaying MKRouteSteps

In lack of a better title...
I'm am writing a custom turn-by-turn implementation in an app used for tracking as well as giving directions using CLLocation and MKMapView.
Getting a route for a given address, displaying it with the returned polyline, getting an ETA and a distance and displaying those all seems fairly trivial and easily implemented. One thing that there isn't a lot of guidance on though, is how to display MKRouteStep's returned in the MKRoute object. All guides online either don't bother mentioning them, or skip over them fairly easily by simply displaying them at once in a table view. Obviously not the most gracious solution, and in my opinion a critical part that's missing.
One solution I did find (can't find the SO answer I saw it in) mentioned creating a CLRegion for each step. So I wen't ahead and created my own implementation of it:
private func drawRouteOnMap() {
viewModel.getRouteForDelivery() { route in
if let route = route {
self.polyline = route.polyline
self.mapView.addOverlay(route.polyline, level: .AboveRoads)
//Add the route steps to the currentRouteSteps array for later retrieval
self.currentRouteSteps = route.steps
//Set the routeStepLabels text property to the first step of the route.
if let step = route.steps.first {
self.routeStepLabel.text = step.instructions
}
//Iterate over the steps in the `MKRoute` object. Create each region to monitor with the identifier set to an Int
var i = 0
for step in route.steps {
let coord = step.polyline.coordinate
let region = CLCircularRegion(center: coord, radius: 20.0, identifier: "\(i)")
self.locationManager.startMonitoringForRegion(region)
i += 1
}
}
}
}
Then I can listen to the func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {} delegate method
private func updateUIForRegion(oldRegion:CLRegion) {
if let regionID = Int(oldRegion.identifier),
steps = currentRouteSteps {
//Fetch the step for the next region
let step = steps[regionID + 1]
routeStepLabel.text = step.instructions
}
}
Then when we exit the view controller
#IBAction func shouldDismissDidPress(sender: AnyObject) {
for (_, region) in locationManager.monitoredRegions.enumerate() {
locationManager.stopMonitoringForRegion(region)
}
dismissViewControllerAnimated(true, completion: nil)
}
This all works fairly well actually. But where things start to fall apart is if a user presses the home button when in the turn by turn navigation VC or if the app crashes. The app will not stop monitoring the regions set up until the locationManager is told to stop monitoring for them, even if the app is force closed. I wouldn't want the app to terminate all the region monitoring anyways, as the navigation wouldn't work when the user resumes the app. It just feels like the region monitoring isn't really the correct way to display the MKRouteStep's, but I'm out of ideas how to do it in another way.
Any ideas / better implementations are welcomed
So, first, the way that works is by definition the most elegant way - so if you have some code that handles every possibility, then by all means go for it!
With that in mind, for cases where there isn't a frank crash but your app is closed could you use one of the many delegate calls in AppDelegate to cease your monitoring?

Resources