I have intregrated mesibo chat sdk in flutter framework all is working good but can't able to have contact letter image after enabling the useLetterTitleImage = true . Can any body can tell what function add for contact image android it is working but getting issue on ios i am sharig file and screen shot as well
Code :
import UIKit
import mesibo
import MesiboUI
class SecondViewController: UIViewController,MesiboDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let ui = Mesibo.getInstance().getUiOptions()
UINavigationBar.appearance().tintColor = UIColor.white
ui?.contactPlaceHolder = nil
ui?.mStatusBarColor = 0xf000000
ui?.mToolbarColor = 0xf000000
ui?.enableBackButton = false
ui?.useLetterTitleImage = true
ui?.emptyUserListMessage = "NO MESSAGES"
Mesibo.getInstance()?.setUiOptions(ui!)
navigationController?.navigationItem.setHidesBackButton(true, animated:false)
navigationController?.pushViewController(MesiboUI.getViewController(navigationController?.delegate), animated: false)
Mesibo.getInstance()?.addListener(self)
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
debugPrint("Back Button pressed.")
}
}
private func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChild(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParent: self)
}
func mesibo_(onConnectionStatus status: Int32) {
print("Connection status: %d", status);
}
func mesibo_(onMessageStatus params: MesiboParams!) {
print(params ?? "")
}
func mesibo_(onMessage params: MesiboParams!, data: Data!) {
print("data")
}
func mesibo_(onMessageFilter params: MesiboParams!, direction: Int32, data: Data!) -> Bool {
return true
}
func mesibo_(onUserProfileUpdated profile: MesiboUserProfile!, action: Int32, refresh: Bool) {
}
func mesibo_(onShowProfile parent: Any!, profile: MesiboUserProfile!) {
}
func mesibo_(onUpdateUserProfiles profile: MesiboUserProfile!) -> Bool {
return true
}
You should set a profile picture for the mesibo UserProfile. If not, it will use a letter titler image
This is the code https://github.com/mesibo/ui-modules-ios/blob/master/Messaging/Messaging/UserListViewController.m#L1149
As you can see, it sets a letter titler image if it can't find the thumbnail.
Related
Please can someone help.I would like to add an alert message once a user clicks on the share button on a pdf viewer, i'm using UIDocumentInteractionController to preview the pdf document. and i wanted to know if there are any delegate methods or functions that i can override, where i can add my alert before opening the sharing sheet?[![enter image description here][1]][1]
class ViewController: UIViewController {
var documentController : UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction private func buttonTapped(_ sender: Any) {
guard let fileURL = Bundle.main.url(forResource: "infomation", withExtension: "pdf") else {return}
documentController = UIDocumentInteractionController.init(url: fileURL)
documentController.delegate = self
documentController.presentPreview(animated: true)
}
}
extension ViewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
func documentInteractionControllerWillPresentOpenInMenu(_ controller: UIDocumentInteractionController) {
}
func documentInteractionControllerWillPresentOptionsMenu(_ controller: UIDocumentInteractionController) {
}
func documentInteractionController(_ controller: UIDocumentInteractionController, willBeginSendingToApplication application: String?) {
}
}
but none of them get called when i click on the share button, even though i have set the delegate to be my view controller.is there a way that i can do this ?
Explanation:
There isn't a way to do that without hacking it together by iterating all the sub-views and override the button's action and target.
One clean way is to create your own preview controller like shown below. If you add the QLPreviewController to UINavigationController, it will automatically create a bottom toolbar which you don't want. To work around this, you add it as a child controller of a regular UIViewController and create a custom navigation bar.
You can also use the UTI for the UIActivityItem to determine how the item can be shared. I've only implemented the preview and basic sharing capabilities, but the rest should be super easy to do, and at least it's more customizable than UIDocumentInteractionController since you have full control of everything.
Now for the code that does all of this:
//
// ViewController.swift
// CustomDocumentController
//
// Created by brandon on 2022-01-13.
//
import UIKit
import QuickLook
import MobileCoreServices
import UniformTypeIdentifiers
private class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
var previewItemTitle: String?
init(title: String, url: URL) {
super.init()
previewItemURL = url
previewItemTitle = title
}
}
class DocumentInteractionController: UIViewController {
private let url: URL
private lazy var navigationBar: UINavigationBar = {
let navigationBar = UINavigationBar()
let navigationItem = UINavigationItem(title: name ?? "Document")
navigationItem.hidesBackButton = true
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onDone))
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(onShare)) //UIImage(systemName: "square.and.arrow.up")
navigationBar.pushItem(navigationItem, animated: false)
return navigationBar
}()
private lazy var previewController: QLPreviewController = {
let previewController = QLPreviewController()
previewController.title = name
previewController.dataSource = self
previewController.delegate = self
previewController.reloadData()
return previewController
}()
var uti: String? {
didSet {
previewController.reloadData()
}
}
var name: String? {
didSet {
previewController.title = name
navigationBar.topItem?.title = name
}
}
init(url: URL) {
self.url = url
super.init(nibName: nil, bundle: nil)
//super.init(rootViewController: self.previewController)
self.modalPresentationStyle = .fullScreen
name = (try? url.resourceValues(forKeys: [.localizedNameKey]))?.localizedName
uti = (try? url.resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
if uti == nil {
if #available(iOS 15.0, *) {
uti = UTType.url.identifier
} else {
uti = String(kUTTypeURL)
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
addChild(previewController)
previewController.didMove(toParent: self)
let separator = UIView()
separator.backgroundColor = .lightGray
view.addSubview(navigationBar)
view.addSubview(separator)
view.addSubview(previewController.view)
NSLayoutConstraint.activate([
navigationBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
navigationBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
separator.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
separator.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
separator.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
separator.heightAnchor.constraint(equalToConstant: 1.0 / UIScreen.main.scale),
previewController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
previewController.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
previewController.view.topAnchor.constraint(equalTo: separator.bottomAnchor),
previewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
[navigationBar, separator, previewController.view].forEach({
$0?.translatesAutoresizingMaskIntoConstraints = false
})
navigationBar.barTintColor = previewController.view.backgroundColor
view.backgroundColor = previewController.view.backgroundColor
}
}
extension DocumentInteractionController: QLPreviewControllerDelegate, QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return PreviewItem(title: name ?? "Document", url: url)
}
}
extension DocumentInteractionController {
#objc
private func onDone() {
self.dismiss(animated: true, completion: nil)
}
#objc
private func onShare() {
let activityViewController = UIActivityViewController(activityItems: [url],
applicationActivities: nil)
activityViewController.excludedActivityTypes = [.assignToContact]
if UIDevice.current.userInterfaceIdiom == .pad {
activityViewController.popoverPresentationController?.sourceView = self.view
}
self.present(activityViewController, animated: true, completion: nil)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "Testing", withExtension: ".md")
let doc = DocumentInteractionController(url: url!)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.present(doc, animated: true, completion: nil)
}
}
}
Screenshots:
I have a SwiftUI project in which I'm presenting a view controller to display an advertisement via MoPub.
Everything works as expected except one thing: when I tap the ad's close button, the ad itself closes but the black screen behind the ad continues to show. I guess the view controller is not being dismissed (but the completion block for dismiss does run).
Here's my code:
class InterstitialAds: UIViewController, MPInterstitialAdControllerDelegate {
var moPubView: MPInterstitialAdController?
func viewControllerForPresentingModalView() -> UIViewController! {
return self
}
//Called when you tap the ad's close button:
func interstitialWillDismiss(_ interstitial: MPInterstitialAdController) {
dismissControllerWithoutReward()
}
func showAd() {
let topViewController = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
self.modalPresentationStyle = .fullScreen
topViewController?.present(self, animated: true) {}
}
func dismissControllerWithoutReward() {
self.dismiss(animated: true) {
print("dismissControllerWithoutReward()") //Successfully prints to console
}
}
override func viewDidLoad() {
let adId = "4f117153f5c24fa6a3a92b818a5eb630" //Test ad unit
self.moPubView = MPInterstitialAdController(forAdUnitId: adId)
if let v = self.moPubView {
v.delegate = self
v.loadAd()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
v.show(from: self)
}
}
super.viewDidLoad()
}
}
Question:
Why isn't the view controller being dismissed, despite the successful call to dismiss?
Thank you!
Edit:
Interestingly, if I wait 0.5 seconds before trying to dismiss the view controller, it dismisses as desired. So, now I've got this code in interstitialWillDismiss(_:) (but I still want to know why this is happening):
func interstitialWillDismiss(_ interstitial: MPInterstitialAdController) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.dismissControllerWithoutReward()
}
}
For getting clear implementation and expected behavior you should wrap InterstitialAds controller in UIViewControllerRepresentable, then connect you SwiftUI side with some InterstitialAdsView which implements from UIViewControllerRepresentable via some isPresented flag binding.
Short example:
class InterstitialAds: UIViewController {
let onFlowCompleted: () -> Void
init(onFlowCompleted: #escaping () -> Void) {
self.onFlowCompleted = onFlowCompleted
// ...
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
onFlowCompleted()
// or somewhere else ...
}
}
struct HomeView: View {
#State var shouldShowInterstitialView = false
var body: some View {
Button {
shouldShowInterstitialView = true
} label: {
Text("Show Ad")
}
.fullScreenCover(isPresented: $shouldShowInterstitialView) {
InterstitialAdsView {
shouldShowInterstitialView = false
}
}
}
}
struct InterstitialAdsView: UIViewControllerRepresentable {
// #Environment(\.presentationMode) var presentationMode
// or
// #Binding var isPresented: Bool
// or
let onFlowCompleted: () -> Void
func makeUIViewController(context: Context) -> InterstitialAds {
InterstitialAds(onFlowCompleted: onFlowCompleted)
}
func updateUIViewController(_ uiViewController: InterstitialAds, context: Context) {
// update if needed
}
}
How can I customize the Firebase UI Auth Picker controller with custom buttons, custom actions, background, loader etc..
I already try to subclass the FUIAuthPickerViewController but we can't access to login buttons
This is how you can create your own class of FUIAuthPickerViewController:
Create FUICustomLoginController.swift with:
import UIKit
import FirebaseUI
import FirebaseAuth
class FUICustomLoginController: ViewController {
var authUI: FUIAuth! = FUIAuth.defaultAuthUI()
var auth: Auth = Auth.auth()
private func didSignIn(auth: AuthCredential?, error: Error?, callBack: AuthResultCallback?) {
let callBack: (AuthDataResult?, Error?) -> Void = { [unowned self] result, error in
callBack?(result?.user, error)
self.authUI.delegate?.authUI?(self.authUI, didSignInWith: result, error: error)
}
if let auth = auth {
self.auth.signInAndRetrieveData(with: auth, completion: callBack)
} else if let error = error {
callBack(nil, error)
}
}
func signIn<T: FUIAuthProvider>(type: T.Type, defaultValue: String? = nil) {
try? self.authUI.signOut() // logout from google etc..
self.authUI.providers.first(where: { $0 is T })?.signIn(withDefaultValue: defaultValue, presenting: self, completion: self.didSignIn)
}
}
Subclass your controller from FUICustomLoginController:
class LoginPickerController: FUICustomLoginController {
override func viewDidLoad() {
super.viewDidLoad()
// Customize authUI if needed
//self.authUI.providers = ...
self.authUI.delegate = self
}
#IBAction func loginFacebook(_ sender: Any) {
self.signIn(type: FUIFacebookAuth.self)
}
#IBAction func loginGoogle(_ sender: Any) {
self.signIn(type: FUIGoogleAuth.self)
}
#IBAction func loginPhone(_ sender: Any) {
self.signIn(type: FUIPhoneAuth.self)
}
}
extension LoginPickerController: FUIAuthDelegate {
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
// perform login actions
}
}
You can customize the default buttons, add images etc.. (a working hack )
class SignInViewController: FUIAuthPickerViewController {
weak var delegate: signInProtocol?
// Unhashed nonce.
fileprivate var currentNonce: String?
var backgView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
for each in view.subviews[0].subviews[0].subviews[0].subviews {
if let button = each as? UIButton {
button.layer.cornerRadius = 20.0
button.layer.masksToBounds = true
///do any other button customization here
}
}
///add background image
let scrollView = view.subviews[0]
scrollView.backgroundColor = .clear
let contentView = scrollView.subviews[0]
contentView.backgroundColor = .clear
let background = UIImage(named: "imagename")
let backgroundImageView = UIImageView(image: background)
backgroundImageView.contentMode = .scaleToFill
view.insertSubview(backgroundImageView, at: 0)
}
}
Currently I bought a source code for a social media app that uses firebase for the sign up/ log in page, but I'm seeing that log in page has no background image and sign up buttons are at the bottom leaving a blank screen on the entire page.
I'm a noobi when it comes to coding in xCode so hope you can help me with adding a background image.
So currently I have 2 files that control the Auth screen (Authclient.swift & WelcomeViewController.swift)
I've been going through the files and looks like "WelcomeViewController.swift" controls the sign in screen... This is the code I have in that file:
import UIKit
import SwiftHEXColors
import Firebase
import FirebaseAuth
import FirebaseAuthUI
import FirebaseGoogleAuthUI
import FirebaseFacebookAuthUI
import FirebaseTwitterAuthUI
import FirebasePhoneAuthUI
class WelcomeViewController: UIViewController, FUIAuthDelegate {
#IBOutlet weak var progressView:UIView? // view shown while data is loading
#IBOutlet weak var welcomeView:UIView? // view when data is loaded. like sign-in or intro
var client:AuthClient?
override func viewDidLoad() {
self.welcomeView?.isHidden = true
self.progressView?.isHidden = false
let config = RemoteConfig.remoteConfig()
#if DEBUG
config.configSettings = RemoteConfigSettings(developerModeEnabled: true)
#endif
config.fetch(withExpirationDuration: 100) { (status, error) -> Void in
if status == .success {
print("Config fetched!")
config.activateFetched()
} else {
print("Config not fetched")
print("Error: \(error?.localizedDescription ?? "No error available.")")
}
self.defineTheme(config)
self.welcomeView?.isHidden = false
self.progressView?.isHidden = true
// if user authorized, go to main page
if (Auth.auth().currentUser) != nil {
self.performSegue(withIdentifier: "auth.mute", sender: nil)
} else {
self.buttonPressed(self)
}
}
}
// FIRAuthUIDelegate
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
if let errorHandler = error as NSError? {
self.showError(errorHandler.localizedDescription)
// print user-info. find more here: https://firebase.google.com/docs/auth/ios/errors
print(errorHandler.userInfo)
} else {
if let currentUser = user {
// update displayname and photo
let name = currentUser.displayName ?? kDefaultUsername
let photo = currentUser.photoURL?.absoluteString ?? kDefaultProfilePhoto
client?.saveUser(userId: currentUser.uid,
name: name,
photo: photo,
override: false)
//user?.sendEmailVerification(completion: nil)
}
self.performSegue(withIdentifier: "auth", sender: nil)
}
}
// Helpers
func showError(_ error:String) {
print("Error: \(error)")
let alert = UIAlertController(title: kAlertErrorTitle, message: error, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: kAlertErrorDefaultButton, style: .default) { (action) in })
self.present(alert, animated: true) {}
}
func defineTheme(_ config:RemoteConfig) {
var primary = UIColor.white
var secondary = UIColor.blue
if let string = config[kPrimaryColor].stringValue, !string.isEmpty {
primary = UIColor(hexString: string)!
}
if let string = config[kSecondaryColor].stringValue, !string.isEmpty {
secondary = UIColor(hexString: string)!
}
UINavigationBar.appearance().barTintColor = primary
UINavigationBar.appearance().tintColor = secondary
UIBarButtonItem.appearance().setTitleTextAttributes(
[NSAttributedStringKey.foregroundColor:secondary], for: UIControlState.normal)
UITabBar.appearance().barTintColor = primary
UITabBar.appearance().tintColor = secondary
UIButton.appearance().tintColor = secondary
}
// Actions
#IBAction func buttonPressed(_ sender: AnyObject) {
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
/*
* Uncommend this lines to add Google and Facebook authorization. But first
* enabled it in Firebase Console. More information you can find here:
* https://firebase.google.com/docs/auth/ios/google-signin
* https://firebase.google.com/docs/auth/ios/facebook-login
*/
let providers: [FUIAuthProvider] = [
// FUIGoogleAuth(),
// FUIFacebookAuth(),
// FUITwitterAuth(),
FUIPhoneAuth(authUI:authUI!),
]
authUI?.providers = providers
/*
kEulaUrl needs to be set in Config.swift file. required for publishing
*/
authUI?.tosurl = URL(string:kEulaUrl)
if (Auth.auth().currentUser) != nil {
self.performSegue(withIdentifier: "auth.mute", sender: nil)
} else {
let authViewController = authUI!.authViewController()
self.present(authViewController, animated: true) {
// ..
}
}
}
}
Can anyone point me in the right direction to add a background image to this screen. Already have my 3 images in Assets.xcassets named bgLogin.imageset.
Thanks
This is what you want to do.
Create an extension of their baseViewController
extension FUIAuthBaseViewController {
Inside of that extension, override their viewWillAppear() and set the image there
open override func viewWillAppear(_ animated: Bool) {
self.navigationItem.leftBarButtonItem = nil
self.view.backgroundColor = .white
// if view is base view add logo as subview
let vc = self.navigationController?.viewControllers.first
if vc == self.navigationController?.visibleViewController {
makeLogoImage()
} else {
// hide the image in proceeding views by covering it with a white background
vc?.view.backgroundColor = .white
}
}
/**
Create imageView and display it at the top of the screen.
*/
func makeLogoImage() {
let imageView = UIImageView(image: UIImage(named: "angel.png"))
let width = view.frame.width
let height = view.frame.height
imageView.frame = CGRect(x: width / 4, y: height / 8 , width: width / 2, height: width / 2)
imageView.contentMode = .scaleAspectFill
self.view.addSubview(imageView)
self.view.sendSubview(toBack: imageView)
}
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
}
}