What am I doing wrong on using USerDefault to save data - ios

I'm encoding images selected by the user, then saving to USerDefault. So, I retrieve these images by decoding and converting to an Array of UIImages, where I populate my UITableView. My goal is being able to terminate the App and when I open again, the UITableView still populated, I want to persist the user data.
Everything worked perfectly, I got what I wanted but after a couple tries I got the following error:
Terminating app due to uncaught exception 'NSRangeException', reason:
' -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty
NSArray'
What would be the best way for me to do what I want?
Here is where the data should be retrieved:
class ViewController: UIViewController, CLLocationManagerDelegate, UITableViewDataSource, dataReceivedDelegate {
func dataReceived(nameSaved: NSArray) {
nameSaved1 = nameSaved
// imagemCell = fotoSaved
self.itensTableView.reloadData()
}
#IBOutlet weak var itensTableView: UITableView!
//Notification
let content = UNMutableNotificationContent()
//
var locationManager:CLLocationManager = CLLocationManager()
var currentLocation: CLLocation?
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: "DD07D751-FBE1-430C-973A-281F9DA59A39")!, identifier: "Estimotes")
var arrayNomes = NSMutableArray()
var nomeReceived = ""
var qtd:Int = 0
var imagemCell = [NSData]()
var nameSaved1 = NSArray()
var dataSaved = [NSData]()
var imageSaved = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
if isKeyPresentInUserDefaults(key: "namesSavedArray") == true{
nameSaved1 = UserDefaults.standard.array(forKey: "namesSavedArray") as! [String] as NSArray
itensTableView.reloadData()
}
if isKeyPresentInUserDefaults(key: "fotoSaved") == true{
// Load the image
imagemCell = UserDefaults.standard.object(forKey: "fotoSaved") as! [NSData]
for uiimage in imagemCell {
let imageConverted = UIImage(data: uiimage as Data)
imageSaved.append(imageConverted!)
itensTableView.reloadData()
}
}
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
//LocationManager
locationManager.delegate = self
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedWhenInUse) {
locationManager.requestWhenInUseAuthorization()
}
locationManager.startRangingBeacons(in: region)
}
func isKeyPresentInUserDefaults(key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addVc" {
let vc:adicionarNovoItemVc = segue.destination as! adicionarNovoItemVc
vc.delegate = self
}
}
func rangeBeacons(){
let uuid = UUID(uuidString: "DD07D751-FBE1-430C-973A-281F9DA59A39")
let major:Int = 1
let minor:Int = 1
let identifier = "Bruz IBeacon"
let region = CLBeaconRegion(proximityUUID: uuid!, major: CLBeaconMajorValue(major), minor: CLBeaconMinorValue(minor), identifier: identifier)
locationManager.startRangingBeacons(in: region)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
if status == .authorizedAlways{
rangeBeacons()
}
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
guard let discoveredBeacon = beacons.first?.proximity else {
print("Beacon nao encontrado"); return}
switch discoveredBeacon {
case .immediate:
beaconMuitoProximo()
self.view.backgroundColor = .green
case .near: break
// beaconProximo()
// self.view.backgroundColor = .orange
case .far:
beaconLonge()
self.view.backgroundColor = .red
case .unknown:
self.view.backgroundColor = .black
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
func beaconMuitoProximo(){
//Notificacoes de perda de objetos
// self.content.title = "Seguro"
// self.content.body = "Suas coisas estao perto de voce =)"
// self.content.sound = UNNotificationSound.default
// let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// let request = UNNotificationRequest(identifier: "testIdentifierPerto", content: self.content, trigger: trigger)
// UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
//
}
func beaconProximo(){
}
func beaconLonge(){
// create a sound ID, in this case its the tweet sound.
let systemSoundID: SystemSoundID = 1016
// to play sound
AudioServicesPlaySystemSound (systemSoundID)
}
#IBAction func botaoAdicionar(_ sender: UIButton) {}
//TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let item = objetos[indexPath.row]
let cell = itensTableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! tableviewCell
cell.nameCell.text = nameSaved1[indexPath.row] as? String//Nil value
cell.imageViewCell.image = imageSaved[indexPath.row] //Nil value
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imageSaved.count
}
}
Here is where I'm saving the data:
protocol dataReceivedDelegate {
func dataReceived(nameSaved:NSArray)
}
class adicionarNovoItemVc: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var textFieldNome: UITextField!
#IBOutlet weak var namePreview: UILabel!
#IBOutlet weak var imagePreview: UIImageView!
let imagePicker = UIImagePickerController()
let picker = UIImagePickerController()
var arrayName = [String]()
var arrayFotoData = [NSData]()
var delegate:dataReceivedDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.textFieldNome.delegate = self
if isKeyPresentInUserDefaults(key: "namesSavedArray") == true{
arrayName = UserDefaults.standard.array(forKey: "namesSavedArray") as! [String]
}
if isKeyPresentInUserDefaults(key: "fotoSaved") == true{
arrayFotoData = UserDefaults.standard.array(forKey: "fotoSaved") as! [NSData]
}
}
func isKeyPresentInUserDefaults(key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
#IBAction func botaoAdcFoto(_ sender: UIButton) {
picker.allowsEditing = true
picker.delegate = self
picker.sourceType = .photoLibrary
if let mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
picker.mediaTypes = mediaTypes
}
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
self.imagePreview.image = image
self.namePreview.text = self.textFieldNome.text
//Encode Image
let dataSaved:NSData = image.pngData()! as NSData
arrayFotoData.append(dataSaved)
UserDefaults.standard.set(arrayFotoData, forKey: "fotoSaved")
}
self.picker.dismiss(animated: true, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.textFieldNome.resignFirstResponder()
return true
}
#IBAction func botaoAdcItem(_ sender: UIButton) {
if (self.namePreview!.text != nil) && (self.imagePreview!.image != nil) {
if delegate != nil {
arrayName.append(self.namePreview.text!)
delegate?.dataReceived(nameSaved: arrayName as NSArray)
UserDefaults.standard.set(arrayName, forKey: "namesSavedArray")
self.navigationController?.popViewController(animated: true)
}
}
else {return}
}
}
That's how the app should work:
This screen is where the UITableView is located, so the data should be persist here:
This screen is where the data should be set and saved:
After press the button, the screen should be dismissed and reload the UITableView containing the data.

I have the Objective-c version of the code which you may need, kindly try to create the swift version.
So, to save/write the image data to the application's folder you can do as
//to write the image in folder
NSData *pngData = UIImagePNGRepresentation(image);
[pngData writeToFile:[[AlertHelper sharedAlertManager] documentsPathForFileName:#"The name for your image"] atomically:YES]; //Write the file
I have used an helper method in the above code to ease the process and use it in at various class files.
And to Read the image data,
NSData *pngData1 = [NSData dataWithContentsOfFile:[[AlertHelper sharedAlertManager] documentsPathForFileName:#"The same name you used to write for your image"]];
UIImage *image = [UIImage imageWithData:pngData1];
And the Helper methood is
// to write and read files in paths
- (NSString *)documentsPathForFileName:(NSString *)name
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSLog(#"%#",[documentsPath stringByAppendingPathComponent:name]); // to check if the name used for image.
return [documentsPath stringByAppendingPathComponent:name];
}
UPDATE:-
For the Swift Version i came across something which may be useful.

I think the problem you are having related to the as! [String]
Just because the key exists doesn’t mean that you won’t get nil back.
Use as? [String] ?? []
So if nil is returned you will have an empty array.

Just call UserDefaults.standard.synchronize() after set new value to UserDefault, it should trigger a synchronization for actually storing new value.

Related

UI Tableview won't display or update search results in cells, Swift

I am an intermediate Swift developer and I am creating an app that involves using a search function to find an address and pinpoint said address on a map. I followed a tutorial on how to achieve this and everything is functional besides the search function itself. Whenever I type in the UIsearchbar my search results tableview controller is instantiated, however, the table view is blank and does not update as I type. An address API call should be present.
Below is my code for the search table
import UIKit
import MapKit
class LocationSearchTable : UITableViewController {
var resultSearchController:UISearchController? = nil
var handleMapSearchDelegate:HandleMapSearch? = nil
var matchingItems:[MKMapItem] = []
var mapView: MKMapView? = nil
#IBOutlet var searchTableView: UITableView!
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
searchController.showsSearchResultsController = true
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = ""
return cell
}
}
extension LocationSearchTable {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = matchingItems[indexPath.row].placemark
handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
dismiss(animated: true, completion: nil)
}
}
This is my code for my initial view controller
import UIKit
import MapKit
import CoreLocation
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
//for some reason my location delegate is not working (resolved)
class locationViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
return
}
var selectedPin:MKPlacemark? = nil
var resultSearchController:UISearchController? = nil
#IBOutlet weak var eventLocationMapView: MKMapView!
#IBOutlet weak var backButton: UIButton!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
resultSearchController?.searchResultsUpdater = self
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
resultSearchController?.searchBar.delegate = self
locationSearchTable.mapView = eventLocationMapView
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
//this is the search bar
navigationItem.titleView = resultSearchController?.searchBar
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.obscuresBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.handleMapSearchDelegate = self
//all of this is in regards to the search functionality
locationManager.startUpdatingLocation()
self.checkLocationAuthorization()
self.checkLocationServices()
self.navigationController?.isNavigationBarHidden = false
// Do any additional setup after loading the view.
locationManager.requestWhenInUseAuthorization()
//this pushes the user request. The issue was most likely in the switch statement
locationManager.delegate = self
locationManager.requestLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//solved it but we will probably have to go back to this
self.centerViewOnUserLocation()
}
#IBAction func backButtonTapped(_ sender: Any) {
self.transitionBackToCreateEventVC()
}
func transitionBackToCreateEventVC (){
let createEventViewController = self.storyboard?.instantiateViewController(identifier: "createEventVC")
self.view.window?.rootViewController = createEventViewController
self.view.window?.makeKeyAndVisible()
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
eventLocationMapView.setRegion(region, animated: true)
//this function centers the map onto the location of the user
}
}
func setUpLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func checkLocationServices(){
if CLLocationManager.locationServicesEnabled(){
setUpLocationManager()
checkLocationAuthorization()
} else {
//show alert letting the user know they have to turn this on
}
}
func checkLocationAuthorization() {
let locationManager = CLLocationManager()
switch locationManager.authorizationStatus {
case .authorizedWhenInUse:
eventLocationMapView.showsUserLocation = true
// We want the users location while the app is in use
break
case .denied:
//show alert instructing how to turn on permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .restricted:
//show an alert
break
case .authorizedAlways:
// we don't want this
break
#unknown default:
return
//I dont know if we need this code
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
extension locationViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 0.05 , longitudinalMeters: 0.05)
eventLocationMapView.setRegion(region, animated: true)
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error)")
}
}
extension locationViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
eventLocationMapView.removeAnnotations(eventLocationMapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
eventLocationMapView.addAnnotation(annotation)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: placemark.coordinate, span: span)
eventLocationMapView.setRegion(region, animated: true)
}
}
I think the you should make a property to hold the MKLocalSearch object in your LocationSearchTable class.
class LocationSearchTable : UITableViewController {
private var search: MKLocalSearch?
// ...
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
// if there is an ongoing search, cancel it first.
self.search?.cancel()
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
// make sure `search` is not released.
self.search = search
}
}
The problem of the original code is that, the search object, instance of MKLocalSearch class will be released when finishes executing updateSearchResultsForSearchController method, since there is no strong reference to it, and start's callback will never be called, so your table view will never be reloaded.
What we do is just make a strong ref to it, and make sure it is not released before completion handler is called.

how can I use a variable from another function swift?

I have a variable by the name of email in the contact picker function. I am trying to use that variable in the IBAction function for the MFMailComposeViewController. I want to apply it to toRecipient. How would I go about using a variable from another function?
import UIKit
import Contacts
import ContactsUI
import MessageUI
class ViewController: UIViewController, CNContactPickerDelegate, MFMailComposeViewControllerDelegate, UITextFieldDelegate {
//Message Setup
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var companyTextField: UITextField!
#IBOutlet weak var lblDetails: UILabel!
#IBAction func btnSelectEmployee(_ sender: Any) {
let entityType = CNEntityType.contacts
let authStatus = CNContactStore.authorizationStatus(for: entityType)
if authStatus == CNAuthorizationStatus.notDetermined {
let contactStore = CNContactStore.init()
contactStore.requestAccess(for: entityType, completionHandler: { (success, nil) in
if success {
self.openContacts()
}
else {
print("Not Authorized")
}
})
}
else if authStatus == CNAuthorizationStatus.authorized {
self.openContacts()
}
}
func openContacts() {
let contactPicker = CNContactPickerViewController.init()
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
picker.dismiss(animated: true) {
}
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
//When user select any contact
let fullName = "\(contact.givenName) \(contact.familyName)"
var email = "Not Available"
if !contact.emailAddresses.isEmpty {
let emailString = (((contact.emailAddresses[0] as AnyObject).value(forKey: "labelValuePair") as AnyObject).value(forKey: "value"))
email = emailString! as! String
self.lblDetails.text = "\(fullName)\n \(email)"
}
}
//Mail View
#IBAction func sendAction(_ sender: Any) {
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = self
mailVC.setSubject("Hello. You have a visitor in the lobby.")
let mailContent = "\(nameTextField.text!) from \(companyTextField.text!) is here to see you."
mailVC.setMessageBody(mailContent, isHTML: false)
let toRecipient = "somebody5555555#gmail.com"
mailVC.setToRecipients([toRecipient])
self.present(mailVC, animated: true) {
self.nameTextField.text = ""
self.companyTextField.text = ""
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
nameTextField.resignFirstResponder()
companyTextField.resignFirstResponder()
return true
}
}
Define the var outside the function
var email = "Not Available"
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
//When user select any contact
let fullName = "\(contact.givenName) \(contact.familyName)"
if !contact.emailAddresses.isEmpty {
let emailString = (((contact.emailAddresses[0] as AnyObject).value(forKey: "labelValuePair") as AnyObject).value(forKey: "value"))
email = emailString! as! String
self.lblDetails.text = "\(fullName)\n \(email)"
}
}
Now you can use the variable inside your class wherever you want.
To get more basics, read documentation Apple Documentation

How to limit the results of a query on Parse behind `query.limit`

I have 8 fake users, including me, with different locations on Parse. If user presses on an annotation on my map, I'd like to get an array with their user.username to open a direct chat with the choosen one among them sending the user.username to my next NewChatVC receiver var via prepareForSegue. In order to achieve this, I'm try'n to create an array closeUsersArray with first, for say, ten people selected among closer ones. Distance filter in km seems to be fine, but when I try to fill my array, in the console I get many repetitions instead only 8 names with:
self.closeUsersArray.append(user.username!) //MARK: Test
or a group/array or filled with repetitions of those 8 names this happens with:
println("this is the array of users * \(self.closeUsersArray) *") //MARK: Test
update
I discovered that in locationManager code with println("evaluates") evaluates multiple times, calling displayLocationInfo with calls createAnnotations multiple times. I think that I should try to clear conditions, maybe are too many
below my file, thanks in advance
import UIKit
import MapKit
import CoreLocation
import Parse
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIActionSheetDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
let locationManager = CLLocationManager()
let kDefaultKmForUserOnMap = 50.0 //default value
var limitNumberForQueryResults = 10
var closeUsersArray:[String] = [] //MARK: Test
let defaults = NSUserDefaults.standardUserDefaults()
var withinKms : Double!
override func viewDidLoad()
{
super.viewDidLoad()
//MARK: checks if the variable is nil, if yes, attributes a value
if defaults.doubleForKey("withinKms") <= 0 {
defaults.setDouble(kDefaultKmForUserOnMap, forKey: "withinKms")
defaults.synchronize()
withinKms = defaults.doubleForKey("withinKms")
println("MapViewController - viewDidLoad - var kKmRadiusForUsersOnMap was never set before, so now is set to \(withinKms) ")
} else {
withinKms = defaults.doubleForKey("withinKms")
println("MapViewController - viewDidLoad - else occurred and var test is \(withinKms)")
}
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
self.segmentedControl.selectedSegmentIndex = 0
withinKms = self.defaults.doubleForKey("withinKms")
println("MapViewController - viewDidAppear - radius shown on map is * \(withinKms) * ")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "fromMapToNewChats" {
//MARK: Hint - this is the standard way to pass data to a NOT embedded VC
var nextVC : NewChatsFromHomeVC = segue.destinationViewController as! NewChatsFromHomeVC
nextVC.calledFromVC = "MapViewController"
nextVC.receivedReceiver = "Specific User"
// // nextVC.filterToParse = self.channleKeywordReceived
}
}
//************************************************
//MARK: send message by touching an annotation
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
println("anotation pressed: \(view.annotation.title)")
self.performSegueWithIdentifier("fromMapToNewChats", sender: self)
}
//************************************************
// MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
let point = PFGeoPoint(latitude:manager.location.coordinate.latitude, longitude:manager.location.coordinate.longitude)
let location = locations.last as! CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
if (error != nil)
{
println("Error: " + error.localizedDescription)
return
}
if placemarks.count > 0
{
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm, point: point)
println("evaluates 3")
}
else
{
println("Error with the data.")
}
})
}
func displayLocationInfo(placemark: CLPlacemark, point: PFGeoPoint)
{
self.locationManager.stopUpdatingLocation()
self.createAnnotations(point, address: "\(placemark.locality) \(placemark.administrativeArea) \(placemark.postalCode) \(placemark.country)")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
println("Error: " + error.localizedDescription)
}
// timelineMessageDataArray.removeAll(keepCapacity: true) //erase previus contents
// println("timelineData cleared")
// MARK: - Create Annotation
func createAnnotations(point: PFGeoPoint, address: String)
{
var query = PFUser.query()
query?.whereKey("location", nearGeoPoint: point, withinKilometers: withinKms)
query?.orderByAscending("location") //MARK: Put list in order
query?.limit = self.limitNumberForQueryResults
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil
{
for(var i = 0; i < objects!.count; i++)
{
let user = objects![i] as! PFUser
var myHomePin = MKPointAnnotation()
let userPoint = user["location"] as! PFGeoPoint
myHomePin.coordinate = CLLocationCoordinate2DMake(userPoint.latitude, userPoint.longitude)
myHomePin.title = user.username
myHomePin.subtitle = address
self.mapView.addAnnotation(myHomePin)
// self.closeUsersArray.append(user.username!) //MARK: Test
}
// println("this is the array of users * \(self.closeUsersArray) *") //MARK: Test
}
else
{
println("Error: " + error!.localizedDescription)
}
})
}
// MARK: - Action Delegate
func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int)
{
switch(buttonIndex)
{
case 0: //Destructive button
break
case 1: // 25.0 Km
// NSUserDefaults.standardUserDefaults().setDouble(25.0, forKey: "withinKms")
self.defaults.setDouble(50.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 2: // 50.0 Km
self.defaults.setDouble(100.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 3: // 100.0 Km
self.defaults.setDouble(200.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 4: // 200.0 Km
self.defaults.setDouble(300.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
default:
break;
}
}
// MARK: - Actions
#IBAction func homeAction(sender: AnyObject)
{
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func indexChanged(sender: UISegmentedControl)
{
switch segmentedControl.selectedSegmentIndex
{
case 0:
println("map clicked") //MARK: this one never evaluates!
case 1:
self.performSegueWithIdentifier("showListView", sender: self)
println("showListView clicked")
default:
break;
}
}
#IBAction func radiusAction(sender: UIButton)
{
// UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "cancel", destructiveButtonTitle: nil, otherButtonTitles: "25.0 Miles", "50.0 Miles", "100.0 Miles", "200.0 Miles").showInView(self.view)
UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "cancel", destructiveButtonTitle: nil, otherButtonTitles: "50 Km", "100 Km", "200 Km", "300 Km").showInView(self.view)
}
#IBAction func profileButton(sender: UIBarButtonItem) {
// let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ProfileNavControllerID") as? UIViewController
//
// self.presentViewController(vc!, animated: true, completion: nil)
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ProfileNavControllerID") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
}
Not sure if this would solve the problem but maybe changing your for loop might help. Try this:
for nearbyUser in objects {
let user = nearbyUser.objectForKey("username")
var myHomePin = MKPointAnnotation()
let userPoint = nearbyUser.objectForKey("location") as! PFGeoPoint
myHomePin.coordinate = CLLocationCoordinate2DMake(userPoint.latitude, userPoint.longitude)
myHomePin.title = ("\(user)")
myHomePin.subtitle = address
self.mapView.addAnnotation(myHomePin)
self.closeUsersArray.append(user) //MARK: Test
}
Try something like that maybe

Swift Pull to Refresh doesn't Show

i've add Pull To Refresh on UITableView on my Swift project successful, on another ViewController i'm not able to show it.
On the others view the code is the same without LocationManager functions.
I don't know where is my error!
Below my code:
import UIKit
import CoreLocation
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var locationCoordinates: CLLocationCoordinate2D!
#IBOutlet weak var bannerView: GADBannerView!
var dati = NSMutableArray()
var datiComplete = NSDictionary()
#IBOutlet weak var tableView: UITableView!
var arrayOfData: [MyData] = [MyData]()
var url:NSURL!
var refreshControl = UIRefreshControl()
var dateFormatter = NSDateFormatter()
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest //Battery drain!
self.locationManager.distanceFilter = 1
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
searchUser()
self.dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
self.dateFormatter.timeStyle = NSDateFormatterStyle.LongStyle
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.insertSubview(refreshControl, atIndex: 0)
self.handleRefresh()
}
func refresh(sender:AnyObject)
{
println("Refresh work!")
self.handleRefresh()
}
func handleRefresh() {
if locationManager.location != nil {
url = NSURL(string: "http://www.myURL.com/data.php?lat=\(locationManager.location.coordinate.latitude)&lon=\(locationManager.location.coordinate.longitude)&max=15&when=now")!
} else {
url = NSURL(string: "http://www.myURL.com/data.php?lat=41&lon=11&max=10&when=now")!
}
//println("Call URL!!")
var request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
var reponseError: NSError?
var response: NSURLResponse?
//var urlData: NSData? = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&reponseError)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if (error != nil) {
return
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var error: NSError?
self.dati = (NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as! NSDictionary)["spots"] as! NSMutableArray
if (error != nil){
return
}
// **** Json Parsing *****
dispatch_async(dispatch_get_main_queue()){
self.tableView.reloadData()
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
})
let now = NSDate()
let updateString = "Last Updated at " + self.dateFormatter.stringFromDate(now)
self.refreshControl.attributedTitle = NSAttributedString(string: updateString)
if self.refreshControl.refreshing
{
self.refreshControl.endRefreshing()
}
self.tableView?.reloadData()
refreshControl.endRefreshing()
}
func searchUser(){
println("Start Search User")
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!){
println("Start Location Manager Func")
self.locationCoordinates = manager.location.coordinate
self.locationManager.stopUpdatingLocation()
println("**************** locations = \(self.locationCoordinates.latitude) \(self.locationCoordinates.longitude)")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.setNavigationBarItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: ViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! ViewCell
cell.backgroundColor = UIColor.whiteColor()
let usr = arrayOfData[indexPath.row]
cell.setCell(<Cell-data>)
return cell
}
var selectedSpot:String? = nil
var selectedSpotIndex:Int? = nil
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "GoSpotDetails" {
var tabBarC : TabBarController = segue.destinationViewController as! TabBarController
var caseIndex = tableView!.indexPathForSelectedRow()!.row
var selectedCase = self.arrayOfSpotsTemp[caseIndex]
tabBarC.DataDetail = selectedCase
}
}
}
Thanks a lot.
Well, your code needs lot of changes. Let me give you some suggestions.
Instead of self.tableView.insertSubview(refreshControl, atIndex: 0) try to use tableView.addSubview(refreshControl). Look thedifference between add and insert subview here
Define another function to populate view. Eg. func populateView()
Replace self.handleRefresh() with self.populateView()
Replace
self.refreshControl.addTarget(
self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged
)
with
self.refreshControl.addTarget(
self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged
)
In func handleRefresh() initiate the refreshing by using refreshControl.beginRefreshing() then remove all the objects, reload the tableView, end refreshing and populate the view.
EDIT
I have misunderstood your question.
Why its not working in other view?
Its because in UITableViewController refreshControl comes pre-fit, a regular ViewController does not.
So what to do?
Here is a snippet defining a lazily instantiated variable which creates and configures a UIRefreshControl:
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(
self, action: "handleRefresh", forControlEvents: .ValueChanged
)
return refreshControl
}()
In viewDidLoad() add UIRefreshControl as a subview to the tableView as:
self.tableView.addSubview(self.refreshControl)
Hope this helps you to understand!

Tutorial for Google autocomplete places api for swift

I would like to have an autocomplete textfield that autocompletes locations for me like the one for android:
https://developers.google.com/places/training/autocomplete-android
Does anyone know where I can find a tutorial for this or an example?
Thanks!
Steps :
Add the Alamofire CocoaPods in your swift project.
Find your Google place API key on Google APIs Console.
Add following code
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let gpaViewController = GooglePlacesAutocomplete(
apiKey: "YOUR GOOGLE PLACE API KEY",
placeType: .Address
)
gpaViewController.placeDelegate = self
presentViewController(gpaViewController, animated: true, completion: nil)
}
}
extension ViewController: GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place) {
println(place.description)
}
func placeViewClosed() {
dismissViewControllerAnimated(true, completion: nil)
}
}
GooglePlacesAutocomplete.swift
import UIKit
import Alamofire
enum PlaceType: Printable {
case All
case Geocode
case Address
case Establishment
case Regions
case Cities
var description : String {
switch self {
case .All: return ""
case .Geocode: return "geocode"
case .Address: return "address"
case .Establishment: return "establishment"
case .Regions: return "regions"
case .Cities: return "cities"
}
}
}
struct Place {
let id: String
let description: String
}
protocol GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place)
func placeViewClosed()
}
// MARK: - GooglePlacesAutocomplete
class GooglePlacesAutocomplete: UINavigationController {
var gpaViewController: GooglePlacesAutocompleteContainer?
var placeDelegate: GooglePlacesAutocompleteDelegate? {
get { return gpaViewController?.delegate }
set { gpaViewController?.delegate = newValue }
}
convenience init(apiKey: String, placeType: PlaceType = .All) {
let gpaViewController = GooglePlacesAutocompleteContainer(
apiKey: apiKey,
placeType: placeType
)
self.init(rootViewController: gpaViewController)
self.gpaViewController = gpaViewController
let closeButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Stop, target: self, action: "close")
gpaViewController.navigationItem.leftBarButtonItem = closeButton
gpaViewController.navigationItem.title = "Enter Address"
}
func close() {
placeDelegate?.placeViewClosed()
}
}
// MARK: - GooglePlaceSearchDisplayController
class GooglePlaceSearchDisplayController: UISearchDisplayController {
override func setActive(visible: Bool, animated: Bool) {
if active == visible { return }
searchContentsController.navigationController?.navigationBarHidden = true
super.setActive(visible, animated: animated)
searchContentsController.navigationController?.navigationBarHidden = false
if visible {
searchBar.becomeFirstResponder()
} else {
searchBar.resignFirstResponder()
}
}
}
// MARK: - GooglePlacesAutocompleteContainer
class GooglePlacesAutocompleteContainer: UIViewController {
var delegate: GooglePlacesAutocompleteDelegate?
var apiKey: String?
var places = [Place]()
var placeType: PlaceType = .All
convenience init(apiKey: String, placeType: PlaceType = .All) {
self.init(nibName: "GooglePlacesAutocomplete", bundle: nil)
self.apiKey = apiKey
self.placeType = placeType
}
override func viewDidLoad() {
super.viewDidLoad()
let tv: UITableView? = searchDisplayController?.searchResultsTableView
tv?.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
}
// MARK: - GooglePlacesAutocompleteContainer (UITableViewDataSource / UITableViewDelegate)
extension GooglePlacesAutocompleteContainer: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.searchDisplayController?.searchResultsTableView?.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
// Get the corresponding candy from our candies array
let place = self.places[indexPath.row]
// Configure the cell
cell.textLabel.text = place.description
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.placeSelected(self.places[indexPath.row])
}
}
// MARK: - GooglePlacesAutocompleteContainer (UISearchDisplayDelegate)
extension GooglePlacesAutocompleteContainer: UISearchDisplayDelegate {
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String!) -> Bool {
getPlaces(searchString)
return false
}
private func getPlaces(searchString: String) {
Alamofire.request(.GET,
"https://maps.googleapis.com/maps/api/place/autocomplete/json",
parameters: [
"input": searchString,
"type": "(\(placeType.description))",
"key": apiKey ?? ""
]).responseJSON { request, response, json, error in
if let response = json as? NSDictionary {
if let predictions = response["predictions"] as? Array<AnyObject> {
self.places = predictions.map { (prediction: AnyObject) -> Place in
return Place(
id: prediction["id"] as String,
description: prediction["description"] as String
)
}
}
}
self.searchDisplayController?.searchResultsTableView?.reloadData()
}
}
}
GooglePlacesAutocomplete.xib
Hope this will help others.
Here's full updated code for Google Autocomplete place API.
Xcode 10.0 & Swift 4.2
Follow this link as to Get Google API KEY.
After Getting the API KEY
Install Cocoa Pods:
source 'https://github.com/CocoaPods/Specs.git'
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
pod 'GooglePlaces'
pod 'GooglePlacePicker'
pod 'GoogleMaps'
end
Appdelegate File:
import UIKit
import GooglePlaces
let GOOGLE_API_KEY = "AIzaSyCuZkL7bh_hIDggnJob-b0cDueWlvRgpck"
#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.
GMSPlacesClient.provideAPIKey(GOOGLE_API_KEY)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
ViewController File:
import UIKit
import GooglePlaces
class ViewController: UIViewController ,CLLocationManagerDelegate{
var placesClient: GMSPlacesClient!
// Add a pair of UILabels in Interface Builder, and connect the outlets to these variables.
#IBOutlet var nameLabel: UILabel!
#IBOutlet var addressLabel: UILabel!
let locationManager = CLLocationManager()
var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined
{
locationManager.requestAlwaysAuthorization()
}
placesClient = GMSPlacesClient.shared()
// self.addToNavbar()
// self.addToSubview()
self.addToPopover()
}
func addToNavbar(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
// Put the search bar in the navigation bar.
searchController?.searchBar.sizeToFit()
navigationItem.titleView = searchController?.searchBar
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
// Prevent the navigation bar from being hidden when searching.
searchController?.hidesNavigationBarDuringPresentation = false
}
func addToSubview(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))
subView.addSubview((searchController?.searchBar)!)
view.addSubview(subView)
searchController?.searchBar.sizeToFit()
searchController?.hidesNavigationBarDuringPresentation = false
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
}
func addToPopover(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
// Add the search bar to the right of the nav bar,
// use a popover to display the results.
// Set an explicit size as we don't want to use the entire nav bar.
searchController?.searchBar.frame = (CGRect(x: 0, y: 0, width: 250.0, height: 44.0))
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: (searchController?.searchBar)!)
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
// Keep the navigation bar visible.
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.modalPresentationStyle = .popover
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
{
print(status)
}
// Add a UIButton in Interface Builder, and connect the action to this function.
#IBAction func getCurrentPlace(_ sender: UIButton) {
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
self.nameLabel.text = "No current place"
self.addressLabel.text = ""
if let placeLikelihoodList = placeLikelihoodList {
print("placeLikelihoodList -- \(placeLikelihoodList)")
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
self.nameLabel.text = place.name
self.addressLabel.text = place.formattedAddress?.components(separatedBy: ", ")
.joined(separator: "\n")
print(place.name)
print(place.coordinate)
print(place.placeID)
print(place.phoneNumber)
print(place.formattedAddress ?? "")
}
}
})
}
}
//MARK: Extentions
// Handle the user's selection.
extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController?.isActive = false
// Do something with the selected place.
print("Place name: \(place.name)")
print("Place address: \(String(describing: place.formattedAddress))")
print("Place attributions: \(place.attributions)")
}
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didFailAutocompleteWithError error: Error){
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
Lightweight Solution!
Instead of using Google framework and Third party library to make simple requests I created a simple library where you can Make a number of Google api requests like Google Autocomplete, Google ReverseGeo , Place Information and Path api for getting path between two location.
To use the library all you have to do is
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
Using Alamofire get the autocomplete Google places result from data, you can show it in table view cell
plist configuration
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Code
import UIKit
import Alamofire
class GooglePlacesViewController: UIViewController,UISearchBarDelegate,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var srchLocation: UISearchBar!
#IBOutlet weak var tblLoction: UITableView!
var arrPlaces = NSMutableArray(capacity: 100)
let operationQueue = OperationQueue()
let currentLat = 51.5033640
let currentLong = -0.1276250
var LocationDataDelegate : LocationData! = nil
var tblLocation : UITableView!
var lblNodata = UILabel()
override func viewDidLoad()
{
super.viewDidLoad()
lblNodata.frame = CGRect(x: 0, y: 80, width:
self.view.frame.size.width, height: self.view.frame.size.height-60)
lblNodata.text = "Please enter text to get your location"
self.view.addSubview(lblNodata)
srchLocation.placeholder = "Ente your location details"
lblNodata.textAlignment = .center
srchLocation.delegate = self
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.beginSearching(searchText: searchText)
}
func beginSearching(searchText:String) {
if searchText.characters.count == 0 {
self.arrPlaces.removeAllObjects()
tblLoction.isHidden = true
lblNodata.isHidden = false
return
}
operationQueue.addOperation { () -> Void in
self.forwardGeoCoding(searchText: searchText)
}
}
//MARK: - Search place from Google -
func forwardGeoCoding(searchText:String) {
googlePlacesResult(input: searchText) { (result) -> Void in
let searchResult:NSDictionary = ["keyword":searchText,"results":result]
if result.count > 0
{
let features = searchResult.value(forKey: "results") as! NSArray
self.arrPlaces = NSMutableArray(capacity: 100)
print(features.count)
for jk in 0...features.count-1
{
let dict = features.object(at: jk) as! NSDictionary
self.arrPlaces.add(dict)
}
DispatchQueue.main.async(execute: {
if self.arrPlaces.count != 0
{
self.tblLoction.isHidden = false
self.lblNodata.isHidden = true
self.tblLoction.reloadData()
}
else
{
self.tblLoction.isHidden = true
self.lblNodata.isHidden = false
self.tblLoction.reloadData()
}
});
}
}
}
//MARK: - Google place API request -
func googlePlacesResult(input: String, completion: #escaping (_ result: NSArray) -> Void) {
let searchWordProtection = input.replacingOccurrences(of: " ", with: ""); if searchWordProtection.characters.count != 0 {
let urlString = NSString(format: "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=%#&types=establishment|geocode&location=%#,%#&radius=500&language=en&key= your key",input,"\(currentLocationLatitude)","\(currentLocationLongtitude)")
print(urlString)
let url = NSURL(string: urlString.addingPercentEscapes(using: String.Encoding.utf8.rawValue)!)
print(url!)
let defaultConfigObject = URLSessionConfiguration.default
let delegateFreeSession = URLSession(configuration: defaultConfigObject, delegate: nil, delegateQueue: OperationQueue.main)
let request = NSURLRequest(url: url! as URL)
let task = delegateFreeSession.dataTask(with: request as URLRequest, completionHandler:
{
(data, response, error) -> Void in
if let data = data
{
do {
let jSONresult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String:AnyObject]
let results:NSArray = jSONresult["predictions"] as! NSArray
let status = jSONresult["status"] as! String
if status == "NOT_FOUND" || status == "REQUEST_DENIED"
{
let userInfo:NSDictionary = ["error": jSONresult["status"]!]
let newError = NSError(domain: "API Error", code: 666, userInfo: userInfo as [NSObject : AnyObject])
let arr:NSArray = [newError]
completion(arr)
return
}
else
{
completion(results)
}
}
catch
{
print("json error: \(error)")
}
}
else if let error = error
{
print(error)
}
})
task.resume()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrPlaces.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let tblCell = tableView.dequeueReusableCell(withIdentifier: "locationCell")
let dict = arrPlaces.object(at: indexPath.row) as! NSDictionary
tblCell?.textLabel?.text = dict.value(forKey: "description") as? String
tblCell?.textLabel?.numberOfLines = 0
tblCell?.textLabel?.sizeToFit()
return tblCell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if LocationDataDelegate != nil
{
let dict = arrPlaces.object(at: indexPath.row) as! NSDictionary
print(dict.value(forKey: "terms") as! NSArray)
let ArrSelected = dict.value(forKey: "terms") as! NSArray
LocationDataDelegate.didSelectLocationData(LocationData: ArrSelected)
}
self.dismiss(animated: true, completion: nil)
}
}
class AddNewAddressVC: UIViewController,UITextFieldDelegate{
func autocompleteClicked() {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))!
autocompleteController.placeFields = fields
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
// Display the autocomplete view controller.
present(autocompleteController, animated: true, completion: nil)
}
#IBAction func action_selectGooglePlaces(_ sender: UIButton) {
autocompleteClicked()
}
}
extension AddNewAddressVC: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("Place name: \(place.name)")
print("Place ID: \(place.placeID)")
print("Place attributions: \(place.attributions)")
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

Resources