I have an application that allows the user to enter some an address, I then want to basically serve up a list of possible addresses that they might be referring to and so that I can use it in MapKit readable form.
My current approach is so:
extension ViewController: UITextFieldDelegate{
func textFieldDidEndEditing(_ textField: UITextField) {
CLGeocoder().geocodeAddressString(textField.text!, completionHandler: {(placemarks: [CLPlacemark]?, error: Error?) -> Void in
if let placemarks = placemarks{
//send placemarks to table view
//however, placemarks only contains one element
//(even when textfield contains very generic text)
} else{ }
})
}
}
Does anyone know how to get more results? Using Apple's Map app and they load 15 results from entering just 4 numbers, how can I get my app to display this many results? Is Apple using something other than coreLocation to bring up possible locations?
EDIT: I think the Google Places API for iOS should help you get what you need. I do not believe Apple has an equivalent API (unless you want plain language searches a la MKLocalSearchRequest).
Related
I'm trying to create an inventory management app for iOS using Swift and the Google Sheets API. Before starting that, I want to get acquainted with the API and make sure everything works properly. To do that, I want to create a function getCell that returns the contents of a cell at a hardcoded row and column (given in A1 notation as required). Since the Sheets API's implementation in Swift seems to require a callback model (I'm a beginner on callbacks and async programming in general), I haven't figured out how to return the cell's contents in my function after the query finishes.
I've followed the Sheets API iOS Quickstart provided by Google (https://developers.google.com/sheets/quickstart/ios?ver=swift&hl=zh-cn) to enable the API in the Developers Console and install the necessary Cocoapods (my Podfile installs 'GoogleAPIClientForREST/Sheets' and 'GoogleSignIn'). I got the included code to run and work properly after converting some parts to newer Swift syntax.
The issue arose when I wrote my getCell function in the ViewController.swift file. As can be seen in the code below, I assumed that the query would finish by the time getCellQuery finishes, so that getCell would be able to access the global variable updated by the callback function (displayResultWithTicket). However, the debug print statements I put in show that the callback function only executes at the end of the program, after getCell checks the global variable and finds nothing new. Note that the spreadsheet ID needs to be replaced with a real one to run this code.
import GoogleAPIClientForREST
import GoogleSignIn
import UIKit
struct MyVariables {
static var currentCell: String? = nil
}
class ViewController: UIViewController, GIDSignInDelegate, GIDSignInUIDelegate {
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
private let service = GTLRSheetsService()
#IBOutlet weak var signInButton: GIDSignInButton!
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes = scopes
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
self.service.authorizer = nil
} else {
self.signInButton.isHidden = true
self.service.authorizer = user.authentication.fetcherAuthorizer()
//main program starts here
print("Cell: " + getCell(a1: "Inventory!A1"))
}
}
func getCell(a1: String) -> String {
print("Started getCell")
getCellQuery(a1: a1)
if MyVariables.currentCell != nil {
print("Finished getCell")
return MyVariables.currentCell!
} else {
print("Error: the global variable is nil and probably hasn't been changed")
print("Finished getCell")
return ""
}
}
func getCellQuery(a1: String) {
print("Started getCellQuery")
let spreadsheetId = "INSERT ID HERE"
// arbitrary row and column in A1 notation
let getRange = a1
let getQuery = GTLRSheetsQuery_SpreadsheetsValuesGet.query(withSpreadsheetId: spreadsheetId, range:getRange)
service.executeQuery(getQuery, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:)))
print("Finished getCellQuery")
}
#objc func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRSheets_ValueRange,
error : NSError?) {
print("Started callback")
if let error = error {
print("\(error.localizedDescription)")
return
}
// turn 2d array into string
let rows = result.values!
for row in rows {
for item in row {
// update global variable
MyVariables.currentCell = item as? String
}
}
print("Finished callback")
}
}
After signing in, I would expect the following in the console:
Started getCell
Started getCellQuery
Started callback
Finished callback
Finished getCellQuery
Finished getCell
Cell: <Whatever the cell has>
Instead, I get this:
Started getCell
Started getCellQuery
Finished getCellQuery
Error: the cell value is nil and probably hasn't been changed
Finished getCell
Cell:
Started callback
Finished callback
Separately, I've also tried using a while loop to wait for the global variable to be updated, but it seems like Swift is consistently opting to call the callback function at the end of the entire program, which is annoying.
If there's a less clunky approach for this simple task, it would be great to know.
It looks like your issue is with async calling patterns. When you call service.executeQuery, it will begin an async query but since it is hitting a web service, that query could take a long time to complete. The executeQuery function returns immediately and the query itself is probably queued by their library. At the next opportunity, the request is sent and when the reply comes back, your displayResultWithTicket(ticket:finishedWithObject:error:) callback is called but in the meantime, the rest of your getCellQuery function completed, as did getCell.
A better pattern would be to fire some notification event in the callback now that you have a response (and pass the value you got back in the notification) and then have a listener for that notification get the response and do the appropriate thing with the result.
Alternatively, there may be a method in the sheets API to pass in a closure which is a lot like a callback but the code for it lives within the function that is passing it so state management is a little easier. I'm not familiar with that API so I'm not sure if that exists.
Here is an article on managing async code in Swift that covers some of the options. It's a little old, but it covers the basics.
I know that there's a built-in template for it.
I go to the File menu and choose New > Target
Select iOS > Application extensions from the left-hand pane.
Now choose Intents extension.
That will create two new groups: YourExtension and YourExtensionUI. If you open the YourExtension group you'll see IntentHandler.swift, which contains some sample code for handling workouts.
Here's a much simpler example to get you started:
class IntentHandler: INExtension, INSendMessageIntentHandling {
override func handler(for intent: INIntent) -> AnyObject {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
print("Send message: " + (intent.content ?? "No message"))
let response = INSendMessageIntentResponse(code: .success, userActivity: nil)
completion(response)
}
}
I did that, it's OK.
Now my issue is about using INStartWorkoutIntent instead of INSendMessageIntent, how am I supposed to? Is there a built-in template for this intents too?
Finally, I solved the question by myself.
When you want to use INStartWorkoutIntent properly, you have just to remove all the built-in template content.
You have also to replace INSendMessageIntentHandling by INStartWorkoutIntent Handling.
public func handle(startWorkout intent: INStartWorkoutIntent, completion: #escaping (INStartWorkoutIntentResponse) -> Swift.Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartWorkoutIntent.self))
let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
DO NOT FORGET:
To your newly created Intents target, fully expand the NSExtension dictionary to study its contents. The dictionary describes in more detail which intents your extension supports and if you want to allow the user to invoke an intent while the device is locked.
Insert the most relevant intents at the top if you want to support more than one. Siri uses this order to figure out which one the user wants to use in case of ambiguity.
We now need to define which intents we want to support.
For example, I want to build an extension that supports the payment intent. Modify the Info.plist file to match the following picture.
Here we specify that we want to handle the INSendPaymentIntent and that we require the device to be unlocked. We don't want strangers to send payments when the device is lost or stolen!
Last thing just to set in target your Intent at the running and it's done.
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!
I am trying to integrate CallDirectory Extension for blocking some incoming call. But application is not even recognising the numbers provided for blocking. Is there anyone who have succeeded in doing this ??
You can see the format that i have used..
private func addIdentificationPhoneNumbers(to context: CXCallDirectoryExtensionContext) throws {
let phoneNumbers: [CXCallDirectoryPhoneNumber] = [ 18775555555, 18885555555,+91949520]
let labels = [ "Telemarketer", "Local business","myPhone"]
for (phoneNumber, label) in zip(phoneNumbers, labels) {
context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
}
}
And , i referred this for development. http://iphoneramble.blogspot.in/2016/07/ios-10-callkit-directory-extension.html
Testing Device & iOS Version - iphone 5s ,iOS 10.1
Atlast , I have got the solution for call blocking. I haven't had a way to check if the call blocking code is working or not. Here are some of the things that i have done for making it work.
Check if your application is running in 64 bit iOS device
(iphone 5s or greater devices)
Adding the numbers in numerically ascending order
Add country code to every number
A sample code for adding mobile numbers for blocking is given below
let phoneNumber : CXCallDirectoryPhoneNumber =
CXCallDirectoryPhoneNumber("+9194******")!
context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
Check your application has given permission to black calls
(setting -> phone -> call Blocking & identification -> Check your app is allowed to block calls)
You can also check the enabledStatus by putting this below code in your viewController
CXCallDirectoryManager.sharedInstance.getEnabledStatusForExtension(withIdentifier:
"bundleIdentifierOfYourExtension", completionHandler:
{(status, error) -> Void in
if let error = error {
print(error.localizedDescription)
}
})
Also , add following code to viewController
CXCallDirectoryManager.sharedInstance.reloadExtension(withIdentifier:
“bundleIdentifierOfYourExtension”, completionHandler: { (error) -> Void in
if let error = error {
print(error.localizedDescription)
}
})
You will find these url's helpful for the development.
http://iphoneramble.blogspot.in/2016/07/ios-10-callkit-directory-extension.html
https://translate.google.com/translate?hl=en&sl=zh-CN&u=http://colin1994.github.io/2016/06/17/Call-Directory-Extension-Study/&prev=search
Kindly please let me know if you have got improved methods and corrections.
Thanks and happy coding.
Good to see Apple listening to enhancement requests with CX. With iOS 13.4, Apple added the ability to open Call Blocking & Identification settings directly from the app.
func openSettings(completionHandler completion: ((Error?) -> Void)? = nil)
https://developer.apple.com/documentation/callkit/cxcalldirectorymanager/3521394-opensettings
The array of phone numbers must be a sorted list of int64's. From smallest to largest. The list will be rejected with an "entries out of order" error otherwise.
I want to add google places autocomplete to xcode with swift, so users can search on a city and press enter, so should the app show that city on the map.
I´m using google maps, so it has to be connected to that map and the search bar would i like to have in the navi-bar (just above the map)
Does anyone know a good tutorial to do that??
For Swift 3:
1- Select your podfile and type : pod 'GooglePlaces'
2- In the appDelegate, add your API Key :
GMSPlacesClient.provideAPIKey("YOUR KEY") (Import the GooglePlaces)
3- Use this code in your viewController that contains the googleMap:
// This code snippet demonstrates adding a
// full-screen Autocomplete UI control
import UIKit
import GooglePlaces
class ViewController: UIViewController {
// TODO: Add a button to Main.storyboard to invoke onLaunchClicked.
// Present the Autocomplete view controller when the button is pressed.
#IBAction func onLaunchClicked(sender: UIButton) {
let acController = GMSAutocompleteViewController()
acController.delegate = self
present(acController, animated: true, completion: nil)
}
}
extension ViewController: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("Place name: \(place.name)")
print("Place address: \(place.formattedAddress)")
print("Place attributions: \(place.attributions)")
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: \(error)")
dismiss(animated: true, completion: nil)
}
// User cancelled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
print("Autocomplete was cancelled.")
dismiss(animated: true, completion: nil)
}
}
Not exactly tutorials, but there are a few resources on Github, with people providing libraries and code samples to achieve what you're asking for.
SPGooglePlacesAutocomplete (https://github.com/spoletto/SPGooglePlacesAutocomplete) is very popular and has many features, but it's written in Objective-C
iOS Google Places Autocomplete (https://github.com/watsonbox/ios_google_places_autocomplete) is more recent, written in Swift, and also contains some examples.
lots of others...
Even without proper tutorials, you can still check out these libraries and understand how their code works to get some inspiration if you want to write your own components.
As a sidetone, Google also has this page, but it's not a dedicated tutorial for iOS apps, just some useful explanations of how their API works: https://developers.google.com/places/documentation/autocomplete#examples
You can try one for wrapper to implement Autocompletion for Place API with Google:
https://github.com/mrugrajsinh/MVAutocompletePlaceSearchTextField
Its very simple drop-in control and subclass of UITextField so using by binding Class with simple UITextField you can achive Auto completion dropdown similar as Uber and many popular app does.
Here's a tutorial I came across which is based on my ios_google_places_autocomplete code.
#Frederik: If you've had a problem with the library, please create an issue describing it.
please check this one 110% work.
http://sweettutos.com/2015/09/30/how-to-use-the-google-places-autocomplete-api-with-google-maps-sdk-on-ios/
Use this latest repo. with swift 2.0
Just download zip file of project and open pod file and write pod 'GoogleMaps' and save it open terminal and install pod.
Enjoy!!!
RKAutoCompletePlaceSearch
Since the question was asked, Google has added UI elements to the iOS SDK for this sort of workflow. Check out the documentation.
Simple and lightweight solution for Swift users !
I also created a library to make these simple request without including any framework of third party library. You can also maintain cache for Autocomplete with the library.
To use to library follow these steps : -
step-1 Import GoogleApiHelper into your project.
step-2 Initialise GoogleApiHelper
GoogleApi.shared.initialiseWithKey("API_KEY")
step-3 Call the methods
var input = GInput()
input.keyword = "San francisco"
GoogleApi.shared.callApi(input: input) { (response) in
if let results = response.data as? [GApiResponse.Autocomplete], response.isValidFor(.autocomplete) {
//Enjoy the Autocomplete Api
} else { print(response.error ?? "ERROR") }
}
You can find the library here