Black bar between keyboard and textField - ios

I ran into a little problem. I am taking a course on iOS development, and I ran into a problem. I'm a perfectionist, and I want to bring applications to perfection, but I can't figure out which way to dig. There is a small black line between the keyboard and the textField that clearly draws attention to itself.
How to be? What to do to remove it? Which way should I drip? Maybe this is a problem in Xcode 12.3? Could this be because IQKeyboardManagerSwift is conflicting with the current version of Xcode? The video I watched didn't have this problem.
AppDelegate.swift (Here I call up the keyboard):
import UIKit
import Firebase
import IQKeyboardManagerSwift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let db = Firestore.firestore()
print(db)
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.enableAutoToolbar = false
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
ChatViewController.swift (In this view the keyboard pops up. Here I added a clear button for the textField):
import UIKit
import Firebase
class ChatViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var messageTextfield: UITextField!
let db = Firestore.firestore()
var messages: [Message] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
title = K.appName
navigationItem.hidesBackButton = true
tableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
loadMessages()
// add clear button in text field
messageTextfield.clearButtonMode = .always
messageTextfield.clearButtonMode = .whileEditing
// попробовать сделать так, чтобы при нажатии на кнопку переходило на новый абзац в Textfield
}
func loadMessages() {
db.collection(K.FStore.collectionName).order(by: K.FStore.dateField).addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print("There was an issue retrieving data from firestore, \(e)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for doc in snapshotDocuments {
let data = doc.data()
if let messageSender = data[K.FStore.senderField] as? String, let messageBody = data[K.FStore.bodyField] as? String {
let newMessage = Message(sender: messageSender, body: messageBody)
self.messages.append(newMessage)
DispatchQueue.main.async {
self.tableView.reloadData()
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
}
}
}
}
}
#IBAction func sendPressed(_ sender: UIButton) {
if let messageBody = messageTextfield.text, let messageSender = Auth.auth().currentUser?.email {
db.collection(K.FStore.collectionName).addDocument(data: [
K.FStore.senderField: messageSender,
K.FStore.bodyField: messageBody,
K.FStore.dateField: Date().timeIntervalSince1970
]) { (error) in
if let e = error {
print("There was an issue saving data to firestore, \(e)")
} else {
print("Successfully saved data")
DispatchQueue.main.async {
self.messageTextfield.text = ""
}
}
}
}
}
#IBAction func logOutPressed(_ sender: UIBarButtonItem) {
do {
try Auth.auth().signOut()
navigationController?.popToRootViewController(animated: true)
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
}
}
extension ChatViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messages[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! MessageCell
cell.label.text = message.body
if message.sender == Auth.auth().currentUser?.email {
cell.leftImageView.isHidden = true
cell.rightImageView.isHidden = false
cell.messageBubble.backgroundColor = UIColor(named: K.BrandColors.lightPurple)
cell.label.textColor = UIColor(named: K.BrandColors.purple)
} else {
cell.leftImageView.isHidden = false
cell.rightImageView.isHidden = true
cell.messageBubble.backgroundColor = UIColor(named: K.BrandColors.purple)
cell.label.textColor = UIColor(named: K.BrandColors.lightPurple)
}
return cell
}
}

The solution to this problem is to add this line of code:
IQKeyboardManager.shared.keyboardDistanceFromTextField = 0

Related

Background Refresh not working in iCloudKit/CoreData, Swift 5, UIKit

iCloudKit/CoreData won't update the UI on any other iCloud connected devices after making a change on the device in hand.
I have one ViewController which holds a UITableView and 2 buttons (Add and Refresh). The Add button successfully adds a new row to the UITableView as well as to the iCloudKit container. Any changes to the UITableView are reflected on the device in hand (immediately) and in the iCloudKit container (eventually - ie, within 10-15 seconds).
The problem is that I can't figure out how to get any other devices' UI to update automatically. I wait several minutes, and there's never any update. I can verify that my other devices are signed in to the same iCloud account by waiting a few minutes and then hitting the Refresh button on those devices. I designed the Refresh button to force a NSFetchRequest from iCloudKit and then use that data to reload the UITableView. If I manually instigate the NSFetchRequest, the data is shared amongst all my devices - but there is never any automatic update based on some background notification.
The code below represents my best attempt to make sense of a wide array of opinions (and quite-possibly out-dated strategies) - and I'm not sure if I may be mixing metaphors in the code I'm posting here. I'm using XCode 12.4, Swift 5, UIKit, iCloudKit and CoreData. In the main.storyboard, I control-dragged to establish the ViewController as the UITableView's delegate. I've also added Background Modes -> Remote notifications and Push Notifications to my project's Signing & Capabilities tab.
Here is the AppDelegate code:
import UIKit
import CoreData
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// note: is this needed? - only one tutorial/answer mentioned this
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// note: this print() statement never executes - so I'm guessing this func is superfluous
print("AppDelegate.application(didRegisterForRemoteNotificationsWIthDeviceToken) - deviceToken=\(deviceToken)")
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
// note: this print() statement never executes - so I'm guessing this func is superfluous
print("AppDelegate.application(didFailToRegisterForRemoteNotificationsWithError) - error=\(error)")
}
// MARK: - UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let dataContainer = NSPersistentCloudKitContainer(name: "coreData_1")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
// note: I've seen SwiftUI tutorials where this wasn't needed
// - the background refreshes seem to be handled entirely by the SwiftUI's implementation of UITableView
let id = "iCloud.iCloud.org.anon.CoreData1"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
let description = dataContainer.persistentStoreDescriptions.first
description?.cloudKitContainerOptions = options
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// this is supposed to make background updates from iCloud available to the context.
dataContainer.viewContext.automaticallyMergesChangesFromParent = true
dataContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
// call this LAST, after the persistentStoreDescriptions configuration.
dataContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return dataContainer
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Here is the ViewController code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var movies: [NSManagedObject] = []
lazy var managedContext = NSManagedObjectContext(concurrencyType:.mainQueueConcurrencyType)
lazy var cloudContainer = NSPersistentCloudKitContainer()
var notificationCount = 0 // for debugging
override func viewDidLoad() {
super.viewDidLoad()
title = "The List"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
// set up core data accessors once - these are used later
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let mgdCtx = appDelegate.persistentContainer.viewContext
managedContext = mgdCtx
cloudContainer = appDelegate.persistentContainer
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// I never receive these notifications - actOnNotification() is never called
NotificationCenter.default.addObserver(self, selector: #selector(actOnNotification), name: .NSPersistentStoreRemoteChange, object: cloudContainer)
updateTableView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// these notifications aren't working for me in UIKit
NotificationCenter.default.removeObserver(self, name: .NSPersistentStoreRemoteChange, object: cloudContainer)
}
#IBAction func refresh(_ sender: UIBarButtonItem) {
// manually refresh UITableView after giving up on background refresh
updateTableView()
// and just display how many times we've recieved notifications
let alert = UIAlertController(title: "NotificationCount", message: "notificationCount=\(notificationCount)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
#objc func actOnNotification() {
// this function never gets called
notificationCount += 1
updateTableView()
}
#objc func updateTableView() {
// load data using iCloudKit + CoreData
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Movie")
let sort = NSSortDescriptor(key: "fileName", ascending: true)
fetchRequest.sortDescriptors = [sort]
do {
movies = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
// then update UI
tableView.reloadData()
}
#IBAction func addName(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)
alert.addTextField(configurationHandler: { textField in textField.placeholder = "Enter name..." })
let saveAction = UIAlertAction(title: "Save", style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {
return
}
self.save(name: nameToSave)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
// update iCloudKit data
guard let entity = NSEntityDescription.entity(forEntityName: "Movie", in: managedContext) else { return }
let person = NSManagedObject(entity: entity, insertInto: managedContext)
person.setValue(name, forKey: "fileName")
do {
try managedContext.save()
movies.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
print("movies=\(movies)")
// then update UI
tableView.reloadData()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection: Int) -> Int {
return movies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// select cellForRowAt in UI
print("ViewController.tableView(cellForRowAt)")
let person = movies[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = person.value(forKeyPath: "fileName") as? String
print("ViewController.tableView.cellForRow(\(indexPath)")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// update iCloudKit data
managedContext.delete(movies[indexPath.row])
do {
try managedContext.save()
// then update UI
movies.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.reloadData()
} catch let error as NSError {
print("Could not delete. \(error), \(error.userInfo)")
}
} else {
print("ViewController.tableView.commit(\(editingStyle)")
}
}
}
I have followed a tutorial using SwiftUI - and the NotificationCenter.default.addObserver() and .removeObserver() calls in the viewWillAppear() and viewWillDisappear() functions do not appear in that tutorial. In fact, all that tutorial did was to add the background Signing & Capabilities to the project - and it worked perfect. I'm guessing that SwiftUI has default behavior for the master/view classes used in the tutorial that are NOT present in my use of UIKit's UITableView.
Any help will be greatly appreciated...

I need to take data from my firebase database and put it into labels, but the data needs to correspond to my tableView cell title

I'm building out a fairly simple app and have run into a problem. Right now My app has a home screen and a table view with the name of an Event in each cell. The data is loading fine from my database to my cell and when you click on the cell it takes you to a new View Controller. I want to put specific info into each label that corresponds to the name of my event. For example if the cell reads STEM Leadership Confrence when I click on it I want the three labels to have text that matches: the date, point value and time frame. Any help would be appreciated, thanks. (Swift 4 and Firebase RT database)
import UIKit
import Firebase
var refa: DatabaseReference!
class TableViewController: UITableViewController {
var posts = [eventStruct]()
#IBOutlet weak var Tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
refa = Database.database().reference()
loadNews()
Tableview?.delegate = self
Tableview?.dataSource = self
}
struct eventStruct {
let Name: String!
let date: String!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "TableToSignUp", sender: self)
}
func loadNews() {
refa.child("Events").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
if let valueDictionary = snapshot.value as? [AnyHashable:String]
{
let Name = valueDictionary["Name"]
let date = valueDictionary["date"]
self.posts.insert(eventStruct(Name: Name, date: date), at: 0)
self.Tableview.reloadData()
}
})
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Tableview.dequeueReusableCell(withIdentifier: "AllEventsCell", for: indexPath)
//let label1 = cell.viewWithTag(1) as? UILabel
cell.textLabel?.text = posts[indexPath.row].Name
//let cell2 = Tableview.dequeueReusableCell(withIdentifier: "AllEventsCell", for: indexPath)
return cell
}
}
class ViewController: UIViewController, GIDSignInUIDelegate {
#IBOutlet weak var Nav: UINavigationBar!
#IBOutlet weak var NumOfParticipants: UITextField!
#IBOutlet weak var TimeFrame: UITextField!
#IBOutlet weak var EndDate: UITextField!
#IBOutlet weak var Points: UITextField!
#IBOutlet weak var Create: UIButton!
#IBOutlet weak var ViewAll: UIButton!
#IBOutlet weak var CreateEventButton: UIButton!
#IBOutlet weak var ViewMyEventsButton: UIButton!
#IBOutlet weak var Name: UITextField!
var ref: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
CreateEventButton?.layer.cornerRadius = 30
CreateEventButton?.clipsToBounds = true
Create?.layer.cornerRadius = 30
Create?.clipsToBounds = true
ViewMyEventsButton?.layer.cornerRadius = 30
ViewMyEventsButton?.clipsToBounds = true
ViewAll?.layer.cornerRadius = 30
ViewAll?.clipsToBounds = true
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().signIn()
// Do any additional setup after loading the view.
}
#IBAction func CreateEvent(_ sender: Any) {
let text: String = (Name.text)!
let date: String = (EndDate.text)!
let point: String = (Points.text)!
let timeFrame: String = (TimeFrame.text)!
let numOfpar: String = (NumOfParticipants.text)!
ref = Database.database().reference()
let now = Date()
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd"
let dateString = formatter.string(from: now)
let newUserData = ["Name": text, "date": date, "Point Value": point, "Time Frame": timeFrame, "Volunteers Needed": numOfpar, "Date Created": dateString] as [String: Any]
ref?.child("Events").childByAutoId().updateChildValues(newUserData)
}
//,; ["Amount of Points": point];["Date End": date]
}
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate{
let userDefault = UserDefaults()
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
if let error = error{
print(error.localizedDescription)
return
}else{
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
let str = email
let me = str?.count
let int = (me!-9)
let secondInt = (me!-8)
let XStr = str?.dropFirst(secondInt)
let NewStr = str?.dropFirst(int)
guard let authentication = user.authentication else{return}
let crendential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signInAndRetrieveData(with: crendential) {(result, error)in
if error == nil {
self.userDefault.set(true, forKey: "usersignedIn")
self.userDefault.synchronize()
if(NewStr == "gmail.com"){
self.window?.rootViewController?.performSegue(withIdentifier: "TeacherSegue", sender: self)
}
else if(XStr == "aisd.net"){
self.window?.rootViewController?.performSegue(withIdentifier: "TeacherSegue", sender: self)
}
}else {
//self.window?.rootViewController?.performSegue(withIdentifier: "Wegue", sender: self)
print(error?.localizedDescription ?? "me")
}
}
}
}
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Use Firebase library to configure APIs
FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
return true
}
#available(iOS 9.0, *)
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any])
-> Bool {
return GIDSignIn.sharedInstance().handle(url,
sourceApplication:options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: [:])
}
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 application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return GIDSignIn.sharedInstance().handle(url,
sourceApplication: sourceApplication,
annotation: annotation)
}
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:.
}
}
Events-
-LdK2x1kNMB6fBw0Oorn
-LdLPGh-_SXIjM7DhjDV
-LdPVnBYQtAtSOyZM1IR-
Date: "2019-04-28"
Name:"STEM Leadership Confrence"
Point Value: "12"
Time Frame: "3-4"
You need to pass the information to the next UIViewController.
First, put all the information that you need inside your eventStruct and update your struct initialization inside the Firebase callback.
struct EventStruct {
...
var timeFrame: String!
var points: Int!
}
If you are using storyboards, you need to implement the tableView(_:didSelectRowAt:) method and use segue to perform the transition to the next view. This link will help you to add an identifier to your segue Xcode, where to assign the segue identifier
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailToSend: EventStruct = posts[indexPath.row]
performSegue(withIdentifier: "showDetailSegue", sender: detailToSend)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ViewController, let detailToSend = sender as? EventStruct {
vc.detail = detailToSend
}
}
If you are using view code, you just need the tableView(_:didSelectRowAt:)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailToSend: EventStruct = posts[indexPath.row]
let vc = ViewController()
vc.detail = detailToSend
self.present(vc, animated: true, completion: nil)
}
After that, in viewDidLoad() of your ViewController assign the information to the labels.
TimeFrame.text = detail.timeFrame
Another advice, take a look at swift best practices, like https://github.com/raywenderlich/swift-style-guide or use SwiftLint in your project.

Using NotificationCenter Observer to Handle Asynchronous Requests

Similar questions to this have been asked so I apologize, but none of them have been able to help me.
I am struggling to return the value from this asynchronous request to Firebase with a completion handler. The value I am retrieving from Firebase is an array and it does exist. But
Here is my function for making the request to Firebase:
class SearchManager {
var searchResults = [String]()
var listOfMosaics = [String]()
// Retrieves company list from Firebase
func getMosaicTitles(completionHandler: #escaping (_ mosaics: [String]) -> ()) {
Database.database().reference().child("mosaics").observeSingleEvent(of: .value, with: { (snapshot) in
guard let allMosaics = snapshot.value as? [String] else {
print("unable to unwrapp datasnapshot")
return
}
completionHandler(allMosaics)
})
}
// resets search results array
func resetSearch() {
searchResults = []
}
// takes list of all mosaics and filters based on search text
func filterAllMosaics(searchText: String) {
searchResults = listOfMosaics.filter { $0.contains(searchText) }
}
}
And in the AppDelegate I call it like this posting a Notification:
let searchManager = SearchManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
makeRootViewLaunchScreen()
FirebaseApp.configure()
searchManager.getMosaicTitles { (results) in
self.searchManager.listOfMosaics = results
NotificationCenter.default.post(name: NSNotification.Name("mosaicsReturned"), object: nil)
self.stopDisplayingLaunchScreen()
}
// Adds border to bottom of the nav bar
UINavigationBar.appearance().shadowImage = UIImage.imageWithColor(color: UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0))
// Override point for customization after application launch.
return true
}
func makeRootViewLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
UIApplication.shared.keyWindow?.rootViewController = viewController
}
// reassigns root view after Firebase request complete
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController")
UIApplication.shared.keyWindow?.rootViewController = viewController
}
In the viewDidLoad of the viewController that supports the tableView that uses the retrieved array to populate it I add a Notification Observer.
var listOfMosaics = [String]()
var searchResults = [String]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
listOfMosaics = searchManager.listOfMosaics
configureSearchBar()
configureSearchBarTextField()
self.tableView.separatorColor = UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0)
NotificationCenter.default.addObserver(self, selector: #selector(updateListOfMosaics), name: NSNotification.Name("mosaicsReturned"), object: nil)
}
#objc func updateListOfMosaics(notification: Notification) {
listOfMosaics = searchManager.listOfMosaics
}
But when I call the below code it doesn't work the arrays print as empty and as a result it doesn't update my tableView.
extension SearchResultsTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchManager.resetSearch()
searchManager.filterAllMosaics(searchText: searchBar.text!)
tableView.reloadData()
print(listOfMosaics)
print(searchResults)
}
}
Thank you in advanced for the help.
This should work for you now. I think you didn't pass the instance of SearchManager from your AppDelegate to your ViewController. I'm guessing you created a new instance of SearchManager in your ViewController, which has an empty array.
Search Manager:
class SearchManager {
var searchResults = [String]()
var listOfMosaics = [String]()
func getMosaicTitles(completionHandler: #escaping (_ mosaics: [String]) -> ()) {
Database.database().reference().child("mosaics").observeSingleEvent(of: .value, with: { (snapshot) in
guard let allMosaics = snapshot.value as? [String] else {
print("unable to unwrapp datasnapshot")
completionHandler([]) // <- You should include this too.
return
}
completionHandler(allMosaics)
})
}
func resetSearch() {
searchResults = []
}
func filterAllMosaics(searchText: String) {
searchResults = listOfMosaics.filter { $0.contains(searchText) }
}
}
View Controller:
class TableViewController: UITableViewController {
var searchManager: SearchManager?
var listOfMosaics = [String]()
var searchResults = [String]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard let searchManager = searchManager else { return }
listOfMosaics = searchManager.listOfMosaics
print("List of mosaics: \(listOfMosaics)")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let searchManager = SearchManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
makeRootViewLaunchScreen()
FirebaseApp.configure()
searchManager.getMosaicTitles { results in
self.searchManager.listOfMosaics = results
self.stopDisplayingLaunchScreen()
}
return true
}
func makeRootViewLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
window?.rootViewController = viewController
window?.makeKeyAndVisible()
}
func stopDisplayingLaunchScreen() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? TableViewController else { return }
let navigationController = UINavigationController(rootViewController: viewController)
viewController.searchManager = searchManager
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
}
As #TNguyen says in his comment, it sounds like you aren't waiting for the async function getMosaicTitles() to complete.
You might want to disable the search bar button while the call is running, and enable it from the completion handler once the call is complete. Then the user won't be able to click the search button until the results have finished loading.
You can fetch the data from the database in a background thread and add a completion block, so that the tableView reloads only after the updated content is fetched.

Swift Firebase persistence

I'm trying to enable Firebase Persistence but my code keeps crashing with:
terminating with uncaught exception of type NSException
I have tried the line of code under my viewDidLoad as well as under DataService but I still get the same crash. what do I need to do to resolve this problem I'm facing
import UIKit
import Firebase
class HomeTeamSelectionVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var club: Clubs!
var player = [Players]()
var playerFirstName = String()
var playerLastName = String()
var playerSelected: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().persistenceEnabled = true //Correct use of????
CLUB_KEY = ""
CLUB_KEY = club.clubKey
tableView.dataSource = self
tableView.delegate = self
DataService.ds.REF_PLAYERS.queryOrdered(byChild: "clubKey").queryEqual(toValue: club.clubKey).observe(.value, with: { (snapshot) in
print("PLAYERS_COUNT: \(snapshot.childrenCount)")
print("PLAYERS_SNAPSHOT: \(snapshot)")
self.player = []
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let playerDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let players = Players(playerKey: key, dictionary: playerDict)
self.player.append(players)
}
}
}
// self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
print("CHET: local error")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return player.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let players = player[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTeamPlayersCell") as? HomeTeamPlayersCell {
cell.configureCell(players)
return cell
} else {
return HomeTeamPlayersCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let players: Players!
players = player[indexPath.row]
print (players.playerKey)
print (players.playerFirstName)
print (players.playerLastName)
dismiss(animated: true, completion: nil)
}
}
From Firebase documentation for persistenceEnabled property:
Note that this property must be set before creating your first Database reference and only needs to be called once per application.
As such, the standard practice is to set it once in your AppDelegate class. For instance:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FIRApp.configure()
FIRDatabase.database().persistenceEnabled = true
...
return true
}

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