Place/City names from a list of CLLocation - ios

Here is my situation. (Using Swift 2.2)
I have a list of coordinates (CLLocation). I need to call the reverseGeocodeLocation to fetch the corresponding Place/City. If I try to loop through the elements there is a chance for some calls to fails as Apple suggest to send one call in a second. So I need to add a delay between each calls as well.
Is there any way to achieve this? Any help is appreciated.
(If we have multiple items with same lat, long we only call the api once)

This code declares a set of locations and looks them up one by one, with at least 1 second between requests:
var locations = Set<CLLocation>()
func reverseGeocodeLocation() {
guard let location = locations.popFirst() else {
geocodingDone()
return
}
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
//Do stuff here
//Dispatch the next request in 1 second
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
self.reverseGeocodeLocation()
}
}
}
func geocodingDone() {
//Put your finish logic in here
}
FYI I used the block syntax for the Timer, but that only works on iOS 10. If you are using iOS 9 or earlier just use the selector version and it works the same way.

Related

How to call API only once in 30min in swift5?

I am calling one API, but that data is not changing frequently and I am storing data in core data. I want that API should call only once in 30 min. is there any better approach of calling API only if it exceed 30 min from last API call. I can think of Timer based, but like to know if there is any other better way to do same?
If you are calling the api randomly then holding a variable in memory for the last successful call might be good enough. If you want the API to be called automatically you'll best approach would be to set up a timer.
If you want your app to prohibit to make a new call in 30 min, then this would be a quick example:
(wrote this for a playground)
var lastCheck: Date?
let minimumMinutes = 60.0
func makeNetworkCall() {
if let lastCheckDate = lastCheck, lastCheckDate.timeIntervalSinceNow < (30 * minimumMinutes) {
debugPrint("Not making call, Didn't go 30 min yet")
return
}
lastCheck = Date()
debugPrint("Making network call!")
// ... make call
}
makeNetworkCall() // Should make call
makeNetworkCall() // Should not make call
makeNetworkCall() // Should not make call
I haven't tested the code above, but it should work.
To just limit service calling for a specific time (ex 30 minutes), you can store last service called date and use it to decide to do a call or not. You can store the date in memory or persistent storage depend on you need.
UserDefaults can be an option to store last date for persistance. There is a sample implementation below;
func saveLastServiceCalledDate() {
UserDefaults.standard.set(Date(), forKey: "lastServiceCallDate")
}
func isCalledInLast30Min() -> Bool {
guard let lastDate = UserDefaults.standard.value(forKey: "lastServiceCallDate") as? Date else { return false }
let timeElapsed: Int = Int(Date().timeIntervalSince(lastDate))
return timeElapsed < 30 * 60 // 30 minutes
}
func serviceCall() {
// ignore if called in last 30 minutes
if isCalledInLast30Min() { return }
// save current date
saveLastServiceCalledDate()
// do service call
}
My suggestion is to use DispatchSourceTimer because it can be restarted at any time.
Call startTimer() in viewDidLoad and in applicationWillBecomeActive to get the most recent data when the application becomes active
var timer : DispatchSourceTimer!
func startTimer()
{
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: 30.0)
timer.setEventHandler {
self.callAPI()
}
timer.activate()
} else {
timer.schedule(deadline:.now(, repeating: 30.0)
}
}
There is no way but timer
1- Create a Timer with 1 minute schedule
2- Timer function checks current timeStamp against a stored 1 say in defaults
3- If stored value is nil or exceeded 30 minutes gap between the current call the api
4- When you call the api update the stored value with the current 1
The reason behind making it a stored value not global is freguently opening and closing the app won't cause non-new api calls
let current = Date().timeIntervalSince1970
let stored = UserDefaults.standard.double(forKey:"stored")
if stored == 0 || current - stored >= 30.0 {
// call the api && update stored value
}
You haven't mentioned whether this should happen in background or foreground? Because based on that only we need to go for the solution. In case if u are wondering about update the data in the background, you should check apples BGTaskBackground. But the problem with this is, you can't decide the time to trigger. You can only give minimumFetchingInterval, which is not guaranteed but will be decided by the system/is.
Incase if you are looking to update in the foreground, just go with the timer approach you are talking about. Use any background queues to do that job. Queues will help you out in dispatching specific task at specific time with delay method.

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

iOS Concurrency issue: method returned before got the pedometer data

like the code below, when I want to return a pedometer data through a method for some connivence, but the method returned earlier than the data be retrieved.I think this maybe a concurrency issue. How could I return the data in a right way for future use?Thx
func queryPedometerTodayTotalData() -> Int {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
print("this print after got data in a background thread:\(pedometerDataOfToday)")
})
print("This should print before last print, and there haven't got the data now: \(pedometerDataOfToday)")
return pedometerDataOfToday
}
You're right about it being a concurrency issue. You should use the result inside the handler of the queryPedometerDataFromDate.
One way of achieving this would be to use a completion block for your queryPedometerTodayTotalData method instead of having it return a value, like this:
func queryPedometerTodayTotalData(completion:((CMPedometerData?)->())) {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
completion(pedometerData)
})
}
func testQueryPedometerTodayTotalData() {
self.queryPedometerTodayTotalData { (data) in
print(data)
}
}
It is a concurrency issue. queryPedometerDataFromDate is an asynchronous method. it executes the completion block whenever iOS deems it (which would usually be after it has retrieved the data) so that is why the 2nd print line prints first and doesnt return your result.
You need to either use the pedometerData from the completion block inside the completion block, or call a method/delegate that will handle the result of the completion block.

Creating a "reactive" API with RxSwift

I'm dipping toes into RxSwift and would like to create a "streaming API" for one of my regular API calls.
My idea is to take the regular call (which already uses observables without any problems) and have a timer fire such calls and send the results on the same observable, so the view controller can update automatically, so instead of doing this (pseudocode follows):
func getLocations() -> Observable<[Location]> {
return Observable<[Location]>.create {
sink in
NSURLSession.sharedSession.rx_JSON(API.locationsRequest).map {
json in
return json.flatMap { Location($0) }
}
}
}
I'd like for this to happen (pseudocode follows):
func getLocations(interval: NSTimeInterval) -> Observable<[Location]> {
return Observable<[Location]>.create {
sink in
NSTimer(interval) {
NSURLSession.sharedSession.rx_JSON(API.locationsRequest).map {
json in
sink.onNext(json.flatMap { Location($0) })
}
}
}
}
Last thing I tried was adding an NSTimer to the mix, but I can't figure out how to take the reference to the sink and pass it around to the method called by the timer to actually send the events down the pipe, given that the handler for the timer must be on a standalone method. I tried throwing in the block timer extensions from BlocksKit but the timer was fired every second instead of being fired at the specified interval, which defeated the purpose.
I've also read about the Interval operator but I'm not sure it's the right way to go.
Any pointers on how to get this right?
The end goal would be to have the timer re-fire only after the previous call has finished (either success or fail).
You should do something like the code below:
func getLocations(interval: NSTimeInterval) -> Observable<[CLLocation]> {
return Observable<[CLLocation]>.create { observer in
let interval = 20.0
let getLocationDisposable = Observable<Int64>.interval(interval, scheduler: MainScheduler.instance)
.subscribe { (e: Event<Int64>) in
NSURLSession.sharedSession.rx_JSON(API.locationsRequest).map {
json in
observer.onNext(json.flatMap { Location($0) })
}
}
return AnonymousDisposable {
getLocationDisposable.dispose()
}
}
}
The code above fire every 20 seconds the API.locationsRequest and send the result on the same observable, Please note that you have to dispose the Interval when the maim observable dispose.

Swift - slowing down "too fast" animation (UIImage updates) -- aka is NSTimer the only option here?

Newbie to IOS programming - learning through Swift. I'm writing a simple "slot machine / dice game".
I'm trying to show the user a flashing sequence of rolls before the "actual" roll appears.
func doFancyDiceRoll() {
for x in 1...100 {
Die1.image = PipsImg[RollOne()]
Die2.image = PipsImg[RollOne()]
Die3.image = PipsImg[RollOne()]
}
}
Die1, etc., are defined as generic UIImage views.
In any case, I'm not seeing the 100x iterations, just the images at the end of the loop. I'm assuming that either it redraws "too fast" or that IOS is trying to be smart, and only draws the last images so as to conserve resources.
I'll wildly guess that I need to either implement some kind of delay here, or, IOS needs to be told to explicitly draw out my images, and not try to outthink my intent.
For the delay, I've seen something about NSTimer, but nothing I saw seems to simply say something like "pause for .05" second, and the whole construct was unclear as they were ObjC examples/conversions.
(Note: I've simplified things here --- normally, I would store the value of RollOne() so I can use it later. I also would like to make an array (or collection?) like Die[1].image, but that is another question.)
========== Edit =======
OK, so I'm following up with more of my original code, merged in with that of #doctordoder so we can discuss a bit better. Hopefully that is kosher. (If this appended question is too long, please advise me on the best way to post a lengthy follow-up directly.)
import UIKit
class ViewController: UIViewController {
//( stripping out stuff unneeded for discussion )
// refers to same label below - works but kosher??
#IBOutlet var btnRoll_x: UIView
#IBAction func btnRoll(sender: AnyObject) {
triggerRoll()
}
var timer : NSTimer? = nil
var rolls : Int = 0
func triggerRoll() {
//hiding is bad UI, but until i know how to "disable & dim"
btnRoll_x.hidden = true
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: "doFancyDiceRoll", userInfo: nil, repeats: true);
}
func doFancyDiceRoll() {
Die1.image = PipsImg[randomInt(6)]
Die2.image = PipsImg[randomInt(6)]
Die3.image = PipsImg[randomInt(6)]
if (++rolls > 10)
{
timer?.invalidate()
timer = nil
rolls = 0 // DCB added this presumed missing line
btnRoll_x.hidden = false //again, need to do this better
}
}
}
Hopefully, my implementation of the code is what would have been intended. I made some minor adjustments for (hopeful) clarity.
Good news is I have working code. I have just enough understanding to get in place, but I'm fuzzy on some details.
Here is what I (think I) know...
We declare an NSTImer object, and a roll counter at the main level of the class. I note that in my original version, I had the roll counter scoped within the rolling function itself. Took me a while to understand why it could not live in the DiceRoll loop itself, but now I do. I'm going to express it poorly, but since the timer is going to call DiceRoll multiple instances, it needs to live outside the function.
The button btnRoll gets touched, and invokes triggerRoll().
To prevent the user from touching the button while we are in progress, which put us into a state where the roll counter never got to zero, I hide the button. (I'll figure how to properly put in in disabled state later.)
The timer is set. It fires every .1 second (within limits), and is set to repeat. (until .invalidate ). And it "calls" the function doFancyDiceRoll via the selector: attribute.
So, the big change as previously noted is that doFancy..Roll() no longer loops. It excites a single instance up updating the images. It checks the counter, and if we reach the limit, we kill the timer, which stops the timer (invalidate). (And I unhide the button, making it available again.)
So, a few things I am concerned/wondering about: I get the value of timers for other things that need to happen periodically (take health away every second, check a GPS position every 10 seconds, etc.). It's seems a odd construct to force a screen refresh.
Frankly, I would have expected to see see something like this:
func doFancyDiceRoll() {
for x in 1...100 {
Die1.image = PipsImg[RollOne()] // and 2 and 3 of course.....
VIewController.forceRedraw <<=== something like this, or maybe
ViewController.wait(.05) <<== this?? I dunno ;-)
}
}
instead we end up with about 20 extra lines or so. I'd be interested in knowing if there other approaches that could work keeping the loop intact.
Anyway, assuming this is the one true way to go, I guess my followup to this is how do I pass parameters, since this is not a "real" function call. Trying
selector: "doFancyDiceRoll(40)"
was not objected to by the IDE, but failed in execution.
I had exactly same problem back in days, entire loop is finished before the view is refreshed as #doctordoder mentioned. Solved with using NSTimer
var rollCount = 0
let arrayOfImages = ["image01", "image02", "image03"]
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("doFancyDiceRoll"), userInfo: nil, repeats: true)
func doFancyDiceRoll() {
if rollCount == 100 {
timer.invalidate
rollCount = 0
{
else {
//get images from array arrayOfImages[rollCount]
rollCount++
}
}
there could be typos, since I have no Xcode right now.
I have basically the same answer as above :(, but I thought I'd post it anyway.
var timer : NSTimer? = nil
var rolls : Int = 0
func doFancyDiceRoll() {
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: "roll", userInfo: nil, repeats: true);
}
func roll() {
println("rolling")
Die1.image = PipsImg[RollOne()]
Die2.image = PipsImg[RollOne()]
Die3.image = PipsImg[RollOne()]
if (++rolls > 100)
{
timer?.invalidate()
timer = nil
}
}
Rather than NSTimer and invalidating, you can use dispatch_after to do the work for you.
func rollDice(howManyTimes: Int) {
die1.image = PipsImg[RollOne()]
die2.image = PipsImg[RollOne()]
die3.image = PipsImg[RollOne()]
if howManyTimes > 0 {
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) / 10.0))
dispatch_after(delayTime, dispatch_get_main_queue()) {
self.rollDice(howManyTimes - 1)
}
}
}
This will run the code for the number of times specified, delaying each time by 0.1 seconds. It works like this: First it sets the images on each die, then, if there are more iterations, it does a dispatch_after to call itself with rollDice(howManyTimes - 1)
With this, you don't need to maintain a NSTimer and it is pretty self contained.

Resources