Within my Swift iOS app, I am using this library to show Google Place auto complete: https://github.com/watsonbox/ios_google_places_autocomplete
This is what I have in my main view controller:
let gpaViewController = GooglePlacesAutocomplete(
apiKey: "myapikey",
placeType: .Address
)
gpaViewController.placeDelegate = self // Conforms to GooglePlacesAutocompleteDelegate
presentViewController(gpaViewController, animated: true, completion: nil)
This works well, but it takes me to a new view. How do I apply the autocomplete on a search field in my main view controller without having to switch over to another view?
Hey use this swift code from GitHub
https://github.com/vijayvir/LeoGoogle/blob/master/AutoCompeteWithSearchBar/LeoGoogleAutoCompleteSearch.swift
#IBOutlet var leoGoogleAutoCompleteSearch: LeoGoogleAutoCompleteSearch!
override func viewDidLoad() {
super.viewDidLoad()
leoGoogleAutoCompleteSearch.closureDidUpdateSearchBarWithPredictions = { predictions in
predictions.map({ (prediction ) -> Void in
print(prediction.placeId ?? "NG" , " 🚸🚸 " , prediction.description ?? "NG" )
})
}
Here I have made the object of autocomplete api which will return the place_id and description of the search through closure . further Developer can modify the code . For request I have use Almofire for get reguest , which is common these days .
more about code
mport Foundation
import GooglePlaces
import Alamofire
import UIKit
typealias LeoGoogleMapACompletionBlock = (AnyObject, AnyObject) -> Void
typealias LeoGoogleMapAFailureBlock = (AnyObject, AnyObject) -> Void
struct LeoGoogleMapsApiPlaceAutocomplete {
static func get(url: URL,
completionHandler: LeoGoogleMapACompletionBlock? = nil,
failureHandler: LeoGoogleMapAFailureBlock? = nil) {
print("🛫🛫🛫🛫🛫🛫🛫 get :", url)
Alamofire.request(url,
method: .get
)
.responseJSON { response in
print(" get 🛬🛬🛬🛬🛬🛬🛬 " )
if let json = response.result.value {
// print("WebServices : - ", json)
completionHandler!(json as AnyObject, response.result as AnyObject)
} else {
failureHandler?("" as AnyObject, "" as AnyObject)
}
}
.responseString { _ in
failureHandler?("" as AnyObject, "" as AnyObject)
// print("responseString Success: \(responseString)")
}
.responseData { _ in
}
}
struct Prediction {
var description : String?
var id : String?
var placeId : String?
init(dictionary : NSDictionary) {
description = dictionary["description"] as? String
id = dictionary["id"] as? String
placeId = dictionary["place_id"] as? String
}
}
var predictions: [Prediction] = []
init(response: AnyObject) {
if let searchList = response["predictions"] as? [Any] {
for object in searchList {
let tempPrediction = Prediction(dictionary: (object as? NSDictionary)!)
predictions.append(tempPrediction)
}
}
}
}
class LeoGoogleAutoCompleteSearch: NSObject {
#IBOutlet weak var searchBar: UISearchBar!
var closureDidUpdateSearchBar : ((LeoGoogleMapsApiPlaceAutocomplete)-> Void)?
var closureDidUpdateSearchBarWithPredictions : (([LeoGoogleMapsApiPlaceAutocomplete.Prediction])-> Void)?
}
extension LeoGoogleAutoCompleteSearch : UISearchBarDelegate {
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
return true
}
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
return true }
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { // called when text changes (including clear)
webserviceSearchBy(text: searchBar.text!)
}
func webserviceSearchBy(text : String) {
let newString = text.replacingOccurrences(of: " ", with: "+")
let url : URL = URL(string: "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=\(newString)&key=AIzaSyAVrXmSPxYourApiPK_ceurWlZJgrpWY")!
LeoGoogleMapsApiPlaceAutocomplete.get(url: url, completionHandler: { (response, _) in
let some : LeoGoogleMapsApiPlaceAutocomplete = LeoGoogleMapsApiPlaceAutocomplete(response: response)
self.closureDidUpdateSearchBar?(some)
self.closureDidUpdateSearchBarWithPredictions?(some.predictions)
}) { (response, _) in
}
}
}
Add these two variables ...
let autoPlaceURLString : String = "https://maps.googleapis.com/maps/api/place/autocomplete/json"
let apiKey = "your api key"
Now Set your UITextfield delegate to self. And on textfield change method call this below method...
fetchAutocompletePlaces(keyword: textField.text!)
You will receive an array of addresses...
func fetchAutocompletePlaces(keyword: String) {
let urlString = "\(autoPlaceURLString)?key=\(apiKey)&input=\(keyword)"
let s = (CharacterSet.urlQueryAllowed as NSCharacterSet).mutableCopy() as! NSMutableCharacterSet
s.addCharacters(in: "+&")
let encodedURL = urlString.addingPercentEncoding(withAllowedCharacters: s as CharacterSet)
Alamofire.request(encodedURL!).responseJSON { (response) in
if response.result.isSuccess {
if let json = response.result.value as? [String: Any] {
let predictions = json["predictions"]
var locations = [String]()
for dict in predictions as! [NSDictionary]{
locations.append(dict["description"] as! String)
}
DispatchQueue.main.async(execute: { () -> Void in
self.strAddressByGoogle = locations
if (self.strAddressByGoogle.count == 0){
self.tblAddress.isHidden = true
}else {
self.tblAddress.isHidden = false
}
self.tblAddress.reloadData()
})
}
}
}
}
Now show this list in a UITableView on the same view.
Why do not use Google Places API for iOS ? and follow the steps to do it in programmatically using fetcher. Link : https://developers.google.com/places/ios-api/autocomplete#get_place_predictions_programmatically
Related
I'm trying to apply search in my app but it shows a random product not the same of what I'm typing at searchBar
method :
static func getSearch ( name : String ,completion : #escaping (_ product : ProductObject) -> ()) {
let path = Firestore.firestore().collection("Products").whereField("name" , isLessThanOrEqualTo: name)
path.addSnapshotListener { (query, error) in
if error != nil {return}
guard let doucments = query?.documents else {return}
for doc in doucments {
if let data = doc.data() as [String: AnyObject]? {
let newData = ProductObject(dictionary: data)
completion (newData)
}
}
}
}
at searchBar text did change :
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.products = []
ProductApi.getSearch(name: searchText) { (pro) in
self.products.append(pro)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
if searchTxt.text?.count == 0 {
DispatchQueue.main.async { searchBar.resignFirstResponder() }
}
collectionView.reloadData()
}
this was the best way I found :
let path = Firestore.firestore().collection("Products").order(by: "name").start(at: [name]).end(at: ["(name) \u{f8ff}"])
So i have this model
class Event: NSObject {
var _eventName: String!
var _venueName : String!
var _eventImage: String!
var eventName: String {
if _eventName == nil {
_eventName = ""
}
return _eventName
}
var venueName: String {
if _venueName == nil {
_venueName = ""
}
return _venueName
}
var eventImage: String {
if _eventImage == nil {
_eventImage = ""
}
return _eventImage
}
init(eventsDict: Dictionary<String, AnyObject>) {
if let venue = eventsDict["venue"] as? Dictionary<String, AnyObject> {
if let venuname = venue["name"] as? String{
self._venueName = venuname
}
if let eventname = eventsDict["name"] as? String {
self._eventName = eventname
}
if let eventimage = eventsDict["coverPicture"] as? String {
self._eventImage = eventimage
}
}
}
And i make it IGListDiffable with this extension.
extension NSObject: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
return isEqual(object)
}
}
So when I'm loading data from hardcoded code like this
var entries = [Event]()
func loadFakeEvents() {
let entries = [
Event(
eventName: "Ζωρζ Πιλαλι Και Η Soufra Band Στο AN Groundfloor - Live Stage!",
venueName: "AN Groundfloor - live stage",
eventImage: "https://scontent.xx.fbcdn.net/v/t31.0-8/s720x720/15936729_1867160333520142_8855370744955080264_o.jpg?oh=8198bc10a8ea61011d7ec1902b34aa01&oe=593D6BC4"
),
Event(
date: "2017-02-18T21:30:00+0200",
name: "Διονύσης Σαββόπουλος at Gazarte I Main Stage 18/02",
venuename: "Gazarte",
eventImage: "https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/16265335_1262826863809003_3636661375515976849_n.jpg?oh=5bb342321a65d33dbc1cc41de266b45e&oe=5907857C"
)
]
self.entries = entries
}
The events are loading fine. As they have to.
But when i'm making an alamofire request, of course, it takse some time to load the data and append them to the empty array of events.
This is the function that I have to call the events
func loadEvents() {
let parameters: Parameters = [
"Some" : "Parameters",
"Some" : "Parameters"
]
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
print(dict) // <-- Check this out
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
}
}
}
}
}
So in the above code i have a print, which prints the json.
And in my
extension LocationViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
let items: [IGListDiffable] = loader.entries as [IGListDiffable]
print(items.count) // <--- another print of items that should be displayed
return items
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return NormalSectionController()
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
Adapter i also print the items that should be displayed.
So when i load the fakeEvents function it prints 2 but when i load them with the normal function it prints 0 and then the JSON from the dict var from the previous code.
Normally i would reloadData() of the collection view.
But with IGListKit what is the trick of sending the Event Class to the CollectionView?
Thanks a lot for your time and i hope i'm not off topic !
Pasting my answer from this same issue on Github in case anyone finds this.
https://github.com/Instagram/IGListKit/issues/468
It looks like you're missing a call to self.adapter.performUpdates(animated: true) after the for-loop when appending to the entries dict:
func loadEvents() {
// ...
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if responseData.result.value != nil {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
// missing this!
self.adapter.performUpdates(animated: true)
// missing that!
}
}
}
}
}
Here is my API code
import UIKit
import Alamofire
import SwiftyJSON
class ItunesApi: NSObject
{
var artistArray:Array <Artist> = []
func downloadData(name:String, _ completionHandler: #escaping (_ result: Array<Artist>) -> Void)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=musicArtist").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.findDiscography(data: JsonResult)
}
completionHandler(self.artistArray)
}
}
func findDiscography (data: JSON)
{
for (subJson):(String, JSON) in data["results"]
{
if let artistName = subJson.1["artistName"].string
{
print(artistName);
self.downloadDiscography(name: artistName)
}
}
}
func downloadDiscography (name: String)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=album").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.createDataModel(name: name, data: JsonResult)
}
}
}
func createDataModel (name: String, data: JSON)
{
var albums:Array <Album> = []
for (subJson):(String, JSON) in data["results"]
{
var thumbnail:String = ""
var title:String = ""
var year:String = ""
if let thumbImage = subJson.1["artworkUrl60"].string
{
thumbnail = thumbImage;
}
if let titleString = subJson.1["collectionName"].string
{
title = titleString;
}
if let releaseDate = subJson.1["releaseDate"].string
{
year = releaseDate;
}
let album = Album(_thumbnail: thumbnail, _title: title, _year: year)
albums.append(album)
}
let artist = Artist(_name: name, _musicStyle: "Rock", _albums: albums as NSArray);
self.artistArray.append(artist);
}
}
And I call here in MyClassTableView.m
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
if let artist = searchBar.text
{
self.itunesApi.downloadData(name: artist, { (array) in
print(array);
})
}
}
Why the copmletionHandler return before all method are called? I want to return in first completionHandeler the result of all method but it return before. The self.itunesApi.downloadData return [] instead of an array filled
I created a UITextField where, when the user writes a link and enters, my textfield disappears and my webView appears.
What I am trying to do is, when the user writes the first time their link, the textfield saves that link and when the user opens again the app, the web view opens directly from the last link that the user wrote in the textfield. Basically the stored link should run the second time.
Here is all my code:
import UIKit
import Foundation
let urlKey = "User URL"
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.hidden = true
self.textField.addTarget(self, action: #selector(ViewController.textFieldDidUpdate(_:)), forControlEvents: UIControlEvents.EditingChanged)
if doesURLExist() {
self.textField.text = getURL()
}
}
// Text Field Delegate
func textFieldDidUpdate(textField: UITextField)
{
// Remove Spaces
textField.text = textField.text!.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
// Validate URL
NSURL.validateUrl(textField.text, completion: { (success, urlString, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if (success)
{
self.saveURL(urlString!)
self.webView.hidden = false
self.textField.hidden = true
let request = NSURLRequest(URL: NSURL(string: urlString!)!)
self.webView.loadRequest(request)
}
else
{
self.webView.stopLoading()
self.webView.hidden = true
}
})
})
}
#IBAction func dismissKeyboard(sender: AnyObject) {
self.resignFirstResponder()
self.view.endEditing(true)
}
func saveURL(urlString: String) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(urlString, forKey: urlKey)
}
func getURL() -> String {
let defaults = NSUserDefaults.standardUserDefaults()
let urlString = defaults.objectForKey(urlKey) as! String
return urlString
}
func doesURLExist() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
guard let _ = defaults.objectForKey(urlKey) where defaults.objectForKey(urlKey) is String else {
return false
}
return true
}
}
Here is my project in GitHub: https://github.com/anappleapp/NSURLvalidation
You'll want to check if the url exists first by calling doesURLExist, if it does, you opt out of presenting that textfield. If it does not exist, call saveURL. NSUserDefaults provides a simple means to store lightweight data.
let urlKey = "User URL"
func saveURL(urlString: String) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(urlString, forKey: urlKey)
}
func getURL() -> String {
let defaults = NSUserDefaults.standardUserDefaults()
let urlString = defaults.objectForKey(urlKey) as! String
return urlString
}
func doesURLExist() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
guard let _ = defaults.objectForKey(urlKey) where defaults.objectForKey(urlKey) is String else {
return false
}
return true
}
So your class should look something like:
import UIKit
import Foundation
let urlKey = "User URL"
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.hidden = true
self.textField.addTarget(self, action: #selector(ViewController.textFieldDidUpdate(_:)), forControlEvents: UIControlEvents.EditingChanged)
if(doesURLExist) {
self.textField.text = getURL()
}
// Demo UI Settings
}
}
// Text Field Delegate
func textFieldDidUpdate(textField: UITextField)
{
// Remove Spaces
textField.text = textField.text!.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
// Validate URL
NSURL.validateUrl(textField.text, completion: { (success, urlString, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if (success) {
self.saveURL(urlString)
self.webView.hidden = false
self.textField.hidden = true
let request = NSURLRequest(URL: NSURL(string: urlString!)!)
self.webView.loadRequest(request)
} else {
self.webView.stopLoading()
self.webView.hidden = true
}
})
})
}
Don't forget to add the original functions to your class.
You should save the entered string to user defaults. When your app opens you should check user defaults to see if there's already a saved string.
Swift 2 code to save your URL to user defaults:
NSUserDefaults.standardUserDefaults().setObject(urlString!, forKey: "EnteredURLString")
Swift 2 code to check whether there's a saved URL string:
if let urlString = NSUserDefaults.standardUserDefaults().stringForKey("EnteredURLString") {
}
I'm encountering a problem where a property of an instance of a class I've created is seemingly losing reference to one its values.
Essentially I have a class like this:
class Channel {
var callbacks: [String: (JSON) -> Void]
var subscribed = false
let name: String
init(name: String) {
self.name = name
self.callbacks = [:]
}
func bind(eventName: String, callback: (JSON) -> Void) {
self.callbacks[eventName] = callback
}
func handleEvent(eventName: String, eventData: String) {
if let cb = self.callbacks[eventName] {
let json = JSON(object: eventData)
cb(json)
}
}
}
and then inside a ViewController I have the following code:
class ViewController: UIViewController {
let wSock = wSocket(key: "afa4d38348f89ba9c398")
func channelSetup() {
var ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
In the println of ch.callbacks it shows that there is a key-value pair in the dictionary.
However, when the channel receives an event later on when there is a message received over the socket, the callback is no longer there. In terms of code, here is the code in full:
import UIKit
class ViewController: UIViewController {
let wSock = wSocketClient(key: "afa4d38348f89ba9c398")
func channelSetup() {
var ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
class wSocketClient {
let connection: Connection
init(key: String, encrypted: Bool = false) {
var url = "SOCKET_URL"
connection = Connection(url: url)
}
func subscribe(channelName: String) -> Channel {
return self.connection.addChannel(channelName)
}
func connect() {
self.connection.open()
}
}
class Connection: WebSocketDelegate {
let url: String
lazy var socket: WebSocket = { [unowned self] in
return self.connectInternal()
}()
let connected = false
var channels = Channels()
init(url: String) {
self.url = url
}
func addChannel(channelName: String) -> Channel {
return Channel(name: channelName)
}
func open() {
if self.connected {
return
} else {
self.socket = connectInternal()
}
}
func connectInternal() -> WebSocket {
let ws = WebSocket(url: NSURL(string: self.url)!)
ws.delegate = self
ws.connect()
return ws
}
func websocketDidReceiveMessage(text: String) {
let data = (text as NSString).dataUsingEncoding(NSUTF8StringEncoding)
let json = JSON(data: data!)
if let channelName = json["channel"].stringValue {
if let internalChannel = self.channels.find(channelName) {
if let eName = json["event"].stringValue {
if let eData = json["data"].stringValue {
internalChannel.handleEvent(eName, eventData: eData) // this is the part of the code where the channel should eventually call the callback
}
}
}
}
}
}
class Channel {
var callbacks: [String: (JSON) -> Void]
var subscribed = false
let name: String
init(name: String) {
self.name = name
self.callbacks = [:]
}
func bind(eventName: String, callback: (JSON) -> Void) {
self.callbacks[eventName] = callback
}
func handleEvent(eventName: String, eventData: String) {
if let cb = self.callbacks[eventName] { // here self.callbacks is empty and the callback has disappeared
let json = JSON(object: eventData)
cb(json)
}
}
}
class Channels {
var channels = [String: Channel]()
func add(channelName: String) -> Channel {
if let channel = self.channels[channelName] {
return channel
} else {
let newChannel = Channel(name: channelName)
self.channels[channelName] = newChannel
return newChannel
}
}
func find(channelName: String) -> Channel? {
return self.channels[channelName]
}
}
So basically when the WebSocket receives some data that is for the given channel, it should check for the event name, and if there is a callback with that event name, call the callback associated to that event name. However, the channel apparently has no callbacks when the handleEvent method is called, even though at the bottom of viewDidLoad it shows as having a callback in the callbacks property for the channel.
Any ideas as to where / why the callback is disappearing?
Update
I've now tried moving the definition of the channel, ch outside of the channelSetup function so it's like this, but with no luck:
class ViewController: UIViewController {
let wSock = wSocket(key: "afa4d38348f89ba9c398")
var ch: Channel = nil
func channelSetup() {
ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
I've solved this but not because it was something going on in Swift that I didn't understand. Instead it was just that the way that I had setup the code meant that there were duplicate channel objects being created and the callback was being added to only one of the channels.