UINavigationController return nil Swift - ios

When i try print(self.navigationController) in viewWillApear or viewDidLoad all ok. But when delegate return response from API print(self.navigationController) return nil. What could it be?
extension EnterpriseList: APIDataDelegate {
func successRequest() { //print(self.navigtionController) == nil
DispatchQueue.main.async {
self.navigationController?.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
Alert.showWarningAlert(withTitle: "Внимание!", andMessage: "Ошибка получения данных, попробуйте чуть позже", whereSender: self)
}
}
}

class EnterpriseList: UIViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSource.makeAPICall()
}
}
extension EnterpriseList: APIDataDelegate {
func successRequest() {
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
}
}
}
protocol APIDataDelegate: class {
func successRequest()
func badRequest()
}
class DataSource {
var delegate: APIDataDelegate?
private let queue = DispatchQueue(label: "Test", qos: .background, attributes: [], autoreleaseFrequency: .inherit, target: nil)
func makeAPICall() {
queue.asyncAfter(deadline: .now() + 2) {[weak self] in
self?.delegate?.successRequest()
}
queue.asyncAfter(deadline: .now() + 10) {[weak self] in
self?.delegate?.successRequest()
}
}
}
The crash is because you are calling the method on deallocated object.
Here is how to fix this:
Make your delegate is weak and you are correctly deallocating the objects
weak var delegate: APIDataDelegate?

Thx #Manoj for answers it's halped me. But i found solution in other. Besides SurveyList i have UserMenu where i create static let view controllers. Removing static i solved my problem.

Related

Protocol-Delegate pattern not notifying View Controller

My Model saves data to Firestore. Once that data is saved, I'd like it to alert my ViewController so that a function can be called. However, nothing is being passed to my ViewController.
This is my Model:
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
func createUserAddedRecipe(
docId:String,
completion: #escaping (Recipe?) -> Void) {
let db = Firestore.firestore()
do {
try db.collection("userFavourites").document(currentUserId).collection("userRecipes").document(docId).setData(from: recipe) { (error) in
print("Data Saved Successfully") // THIS OUTPUTS TO THE CONSOLE
// Notify delegate that data was saved to Firestore
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
}
}
catch {
print("Error \(error)")
}
}
}
The print("Data Saved Successfully") outputs to the console, but the delegate method right below it doesn't get called.
And this is my ViewController:
class ViewController: UIViewController {
private var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
}
}
extension ViewController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
if dataSavedSuccessfully == true {
print("Result is true.")
}
else {
print("Result is false.")
}
print("Protocol-Delegate Pattern Works")
}
}
Is there something I'm missing from this pattern? I haven't been able to notice anything different in the articles I've reviewed.
So I test your code and simulate something like that
import UIKit
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
// I use this timer for simulate that firebase store data every 3 seconds for example
var timer: Timer?
func createUserAddedRecipe(
docId:String) {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
})
}
}
class NavigationController: UINavigationController {
var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
// Call this method to register for network notification
model.createUserAddedRecipe(docId: "exampleId")
}
}
extension NavigationController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
print(#function)
}
}
so you can see the result as image below, my delegate update controller that conform to that protocol.

Calling function inside function in another function

I'm new in iOS Programming, and I had this problem. Let's say I have these two function:
class BaseViewController: UIViewController, ErrorMessageDelegate {
var uiView = UIView();
var viewErrorMessage:ErrorMessage!
func refresh(_sender: AnyObject) {
print("testing")
}
func getErrorMessage(message:String) {
super.viewDidLoad()
Dialog.dismiss()
ErrorMessage.message = message
viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as! ErrorMessage
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
func removeSubView() {
viewErrorMessage.removeFromSuperview()
}
}
}
I want to call function removeSubView inside function refresh. I had to do that because I need to override refresh function to my subclass. And I need to put the function removeSubView in getErrorMessage because I should to. Does anyone know how to do that?
Please refer below code. It will help you to resolve the issue.
Code:
class BaseViewController: UIViewController, ErrorMessageDelegate {
var uiView = UIView();
var viewErrorMessage:ErrorMessage!
func refresh(_sender: AnyObject) {
removeSubView()
}
func getErrorMessage(message:String) {
super.viewDidLoad()
Dialog.dismiss()
ErrorMessage.message = message
viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as! ErrorMessage
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
}
func removeSubView() {
viewErrorMessage.removeFromSuperview()
}
}
Yes, that is possible
func a() {
c()
}
func b() {
c()
}
func c() {
print("hello world")
}
no it is not possible - but I wonder why you would do that?

My custom RxDelegateProxy immediately disposing

I have some third party library that has delegate methods. But I like using RX so I should create RxDelegateProxy to receive delegate's callbacks via RX.
Here my custom DelegateProxy class:
extension Reactive where Base:GalleryController {
var selectedImages:Observable<(GalleryController, [Image])> {
let proxy = RxGalleryDelegateProxy.createProxy(for: self.base)
return proxy.imageSubject.asObservable()
}
}
private class RxGalleryDelegateProxy: DelegateProxy<GalleryController, GalleryControllerDelegate>, DelegateProxyType, GalleryControllerDelegate {
private var _imageSubject: PublishSubject<(GalleryController, [Image])>?
public weak fileprivate(set) var galleryController: GalleryController?
internal var imageSubject: PublishSubject<(GalleryController, [Image])> {
if let subject = _imageSubject {
return subject
}
let subject = PublishSubject<(GalleryController, [Image])>()
_imageSubject = subject
return subject
}
static func currentDelegate(for object: GalleryController) -> GalleryControllerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: GalleryControllerDelegate?, to object: GalleryController) {
object.delegate = delegate
}
static func registerKnownImplementations() {
self.register { RxGalleryDelegateProxy(parentObject: $0) }
}
private init(parentObject: GalleryController) {
self.galleryController = castOrFatalError(parentObject)
super.init(parentObject: parentObject, delegateProxy: RxGalleryDelegateProxy.self)
}
func galleryController(_ controller: GalleryController, didSelectImages images: [Image]) {
if let subject = _imageSubject {
subject.on(.next((controller, images)))
}
self._setForwardToDelegate(galleryController(controller, didSelectImages: images), retainDelegate: true)
}
deinit {
_imageSubject?.on(.completed)
}
}
In my UIViewController I subscribe with:
final class PhotoLibraryViewController: UIViewController {
private let _bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let gallery = GalleryController()
present(gallery, animated: true, completion: nil)
gallery.rx.selectedImages
.debug("--------")
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (controller, images) in
print("\(images)")
}, onError: { (error) in
DDLogError("Error:\n\(error)")
})
.disposed(by: _bag)
}
}
But all I get in console output via .debug("--------") is:
2018-01-09 20:05:14.814: -------- -> subscribed
2018-01-09 20:05:14.817: -------- -> Event completed
2018-01-09 20:05:14.817: -------- -> isDisposed
So my object is immediately disposing just after creation. What did I do wrong?
Your gallery object is created inside viewDidLoad(), so once viewDidLoad() is completed, the deinit method from gallery is called and it is disposed.
Move your gallery object out of the viewDidLoad():
final class PhotoLibraryViewController: UIViewController {
private let _bag = DisposeBag()
let gallery = GalleryController()
override func viewDidLoad() {
super.viewDidLoad()
present(gallery, animated: true, completion: nil)
gallery.rx.selectedImages
.debug("--------")
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (controller, images) in
print("\(images)")
}, onError: { (error) in
DDLogError("Error:\n\(error)")
})
.disposed(by: _bag)
}
}

Sinch Video calling sound is coming from front speaker

I have implemented sinch video calling in ios swift project i have followed all process given in sinch implementation document https://www.sinch.com/docs/video/ios/#calling. And i am successfully able to implement but i am getting on issue my video sound in coming from front speaker. how can i solve this problem?? Below my code:
var client: SINClient?
var sinCall : SINCall?
Configuring sinch
//MARK: Configuring Sinch Delegate
func configuringSinch(){
//Configuring Client Key
client = Sinch.client(withApplicationKey: Constants.SINCH_APP_KEY, applicationSecret: Constants.SINCH_PRIVATE_KEY, environmentHost: Constants.SANDBOX_ENVIRONMENT, userId: Utility().getUserId())
client?.call().delegate = self
client?.setSupportCalling(true)
client?.enableManagedPushNotifications()
client?.start()
client?.startListeningOnActiveConnection()
let vcCont = client?.videoController()
self.vwLocalView.addSubview((vcCont?.localView())!)
self.sinCall?.delegate = self
}
//MARK: Sinch Video Call Delegate
func clientDidStart(_ client: SINClient!) {
print("Client Did Start")
}
func clientDidFail(_ client: SINClient!, error: Error!) {
print("Client failed : \(error)")
player?.stop()
}
func clientDidStop(_ client: SINClient!) {
print("Client Did Stop")
player?.stop()
}
//MARK: Video Call Did Recieve
func client(_ client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("Did Recieve Incoming Call")
playRingtoneSound() // Playing Audio
call.delegate = self;
self.sinCall = call
}
//MARK: Call Did Add Video Track
func callDidAddVideoTrack(_ call: SINCall!) {
let videoCont = client?.videoController()
vwRemoteView.addSubview((videoCont?.remoteView())!)
}
func callDidEnd(_ call: SINCall!) {
sinCall?.hangup()
}
This is how you can manage SINAudioController to manage audio output.
func audioController() -> SINAudioController {
return (client?.audioController())!
}
//MARK: Video Call Did Recieve
func client(_ client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
audioController().enableSpeaker()
playRingtoneSound() // Playing Audio
call.delegate = self;
self.sinCall = call
}
// In SINCallDelegate
func callDidEstablish(_ call: SINCall!) {
//to disableSpeaker
audioController().disableSpeaker()
}
try this to manage AudioOutput Session manually
// MARK: AudioOutput Session
// to enable front speaker manually
func setSessionPlayerOn()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.none)
} catch _ {
}
}
// to enable speaker manually
func setSessionPlayerSpeaker()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
} catch _ {
}
}
// to turnoff AudioOutput Session manually
func setSessionPlayerOff()
{
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch _ {
}
}
Use this function
func callDidEstablish(_ call: SINCall!) {
let audio = APPDELEGATE.client?.audioController()
audio?.disableSpeaker()
// self.startCallDurationTimerWithSelector()
appDelegate.client?.audioController().stopPlayingSoundFile()
}
this is work for me.
class MainVC: UIViewController,SINCallDelegate {
// var client: SINClient?
private let videoController = SinchManager.sharedInstance.client!.videoController()
private let audioController = SinchManager.sharedInstance.client!.audioController()
private let callClient: SINCallClient
private var call: SINCall!
let username: String
#IBOutlet weak var otherView: UIView!
// private var mainView: SinchView { return view as! SinchView }
#IBAction func call_btn(_ sender: UIButton) {
answer()
}
#IBAction func end_btn(_ sender: UIButton) {
decline()
}
override func loadView() {
// view = SinchView()
view = otherView
}
init(username: String) {
self.username = username
self.callClient = SinchManager.sharedInstance.client!.call()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
print("init(coder:) has not been implemented " + String(describing: aDecoder))
fatalError("init(coder:) has not been implemented " + String(describing: aDecoder))
}
override func viewDidLoad() {
super.viewDidLoad()
call.delegate = self
//self.mainView.videoView.addSubview(self.videoController.localView())
otherView.addSubview((self.videoController?.localView())!)
self.videoController?.localView().contentMode = .scaleToFill
if self.call.direction == SINCallDirection.incoming {
self.audioController?.startPlayingSoundFile(self.pathForSound(string: "incoming.wav") as String, loop: true)
}
if self.call.details.isVideoOffered {
print("video offered")
//self.mainView.videoView.addSubview(self.videoController.localView())
otherView.addSubview((self.videoController?.localView())!)
self.videoController?.localView().contentMode = .scaleToFill
}
otherView.addSubview((self.videoController?.localView())!)
// mainView.answerButton.addTarget(self, action: #selector(answer), forControlEvents: .TouchUpInside)
// mainView.declineButton.addTarget(self, action: #selector(decline), forControlEvents: .TouchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.audioController?.enableSpeaker()
}
func pathForSound(string: String) -> NSString {
let nsSt = Bundle.main.resourcePath! as NSString
return nsSt.appendingPathComponent(string) as NSString
}
func answer() {
call.answer()
}
func decline() {
call.hangup()
}
func callDidEstablish(call: SINCall!) {
print("callDidEstablish")
let audio = SinchManager.sharedInstance.client!.audioController()
audio?.disableSpeaker()
// self.startCallDurationTimerWithSelector()
SinchManager.sharedInstance.client!.audioController().stopPlayingSoundFile()
}
func callDidEnd(call: SINCall!) {
print("callDidEnd")
}
func callDidProgress(call: SINCall!) {
print("callDidProgress")
self.audioController?.startPlayingSoundFile(self.pathForSound(string: "ringback.wav") as String, loop: true)
}
func callDidAddVideoTrack(call: SINCall!) {
print("callDidAddVideoTrack")
otherView.addSubview((self.videoController?.localView())!)
}

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