How do I display contacts when UITextField is clicked? - ios

I have tried multiple times to create a UITextField that when clicked should show the contacts available on the device and retrieve the phone number and display it in the textfield. However I have been unable to do that. The best that I could do is to use a button to receive and display the number on a textfield. This works! How do I do the same for when the UITextField is clicked?
I'm running it on Xcode 10
private let contactPicker = CNContactPickerViewController()
override func viewDidLoad() {
super.viewDidLoad()
configureTextFields()
configureTapGesture()
phonenumber.textContentType = .telephoneNumber
}
private func configureTextFields() {
phonenumber.delegate = self
}
private func configureTapGesture(){
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(SelfTestTimer.handleTap))
viewcontact.addGestureRecognizer(tapGesture)
}
#objc private func handleTap(){
viewcontact.endEditing(true)
}
#IBAction func pbbbbb(_ sender: Any) {
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
}
extension SelfTestTimer: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let phoneNumberCount = contact.phoneNumbers.count
guard phoneNumberCount > 0 else {
dismiss(animated: true)
return
}
if phoneNumberCount == 1 {
setNumberFromContact(contactNumber: contact.phoneNumbers[0].value.stringValue)
}else{
let alertController = UIAlertController(title: "Select one of the numbers", message: nil, preferredStyle: .alert)
for i in 0...phoneNumberCount-1 {
let phoneAction = UIAlertAction(title: contact.phoneNumbers[i].value.stringValue, style: .default, handler: {
alert -> Void in
self.setNumberFromContact(contactNumber: contact.phoneNumbers[i].value.stringValue)
})
alertController.addAction(phoneAction)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
dismiss(animated: true)
self.present(alertController, animated: true, completion: nil)
}
}
func setNumberFromContact(contactNumber: String) {
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
guard contactNumber.count >= 10 else {
dismiss(animated: true) {
self.presentAlert(alertTitle: "", alertMessage: "A maximum of 10 contacts allowed per session", lastAction: nil)
}
return
}
phonenumber.text = String(contactNumber.suffix(10))
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
}
extension SelfTestTimer: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
I want it so that when the UITextField is clicked, the contacts will appear and when one contact is selected, the number should appear in the textfield

You should use textFieldShouldBeginEditing method. Open the contacts controller in this method and return false, no need to add a gesture recogniser.

Related

Selected text doesn't appear on the UITextField

Whenever I click on a UITextField, a list of contacts appear and when a contact is clicked, the phone number should appear in the textfield that was clicked. I currently have 3 textfields and each time I select a contact, it updates only the first textfield even if for example I have selected the 2nd textfield. How do I go about fixing it so that the phone number appears in the corresponding textfield that was selected?
I'm using Xcode 10 and I think that the issue is arising from the func setNumberFromContact
#IBOutlet weak var phonenumber: UITextField!
#IBOutlet weak var phonenumber1: UITextField!
#IBOutlet weak var phonenumber2: UITextField!
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let phoneNumberCount = contact.phoneNumbers.count
guard phoneNumberCount > 0 else {
dismiss(animated: true)
//show pop up: "Selected contact does not have a number"
return
}
if phoneNumberCount == 1 {
setNumberFromContact(contactNumber: contact.phoneNumbers[0].value.stringValue)
}else{
let alertController = UIAlertController(title: "Select one of the numbers", message: nil, preferredStyle: .alert)
for i in 0...phoneNumberCount-1 {
let phoneAction = UIAlertAction(title: contact.phoneNumbers[i].value.stringValue, style: .default, handler: {
alert -> Void in
self.setNumberFromContact(contactNumber: contact.phoneNumbers[i].value.stringValue)
})
alertController.addAction(phoneAction)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
dismiss(animated: true)
self.present(alertController, animated: true, completion: nil)
}
}
func setNumberFromContact(contactNumber: String) {
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
guard contactNumber.count >= 10 else {
dismiss(animated: true) {
self.presentAlert(alertTitle: "", alertMessage: "A maximum of 10 contacts allowed per session", lastAction: nil)
}
return
}
phonenumber.text = String(contactNumber.suffix(10))
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
}
extension SelfTestTimer: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.hasText{
//dont do anything
}else{
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
return
}
The reason your solution is updating only one text field is because you're updating the text of only that text field. In this line phonenumber.text = String(contactNumber.suffix(10)) you change only phonenumber's text. A good solution would be as follows:
Create a temp UITextField to store selected text field reference
#IBOutlet weak var phonenumber: UITextField!
#IBOutlet weak var phonenumber1: UITextField!
#IBOutlet weak var phonenumber2: UITextField!
var currentTextField: UITextField?
And use that text field in text field delegate methods
extension SelfTestTimer: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
currentTextField = nil
textField.resignFirstResponder()
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.hasText{
//dont do anything
}else{
currentTextField = textField
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
return
}
}
Assign selected contact number in that text field
func setNumberFromContact(contactNumber: String) {
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
guard contactNumber.count >= 10 else {
dismiss(animated: true) {
self.presentAlert(alertTitle: "", alertMessage: "A maximum of 10 contacts allowed per session", lastAction: nil)
}
return
}
currentTextField?.text = String(contactNumber.suffix(10))
}

Contact picker in ios to get phone number

I need an option like image picker for picking contact and to display phone number i have managed to get contact names using below code
by using this code it only returns the names , need an option to pick contact from contact list
import ContactsUI
and include - CNContactPickerDelegate
import ContactsUI
class YourViewController: CNContactPickerDelegate{
//MARK:- contact picker
func onClickPickContact(){
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self
contactPicker.displayedPropertyKeys =
[CNContactGivenNameKey
, CNContactPhoneNumbersKey]
self.present(contactPicker, animated: true, completion: nil)
}
func contactPicker(_ picker: CNContactPickerViewController,
didSelect contactProperty: CNContactProperty) {
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
// You can fetch selected name and number in the following way
// user name
let userName:String = contact.givenName
// user phone number
let userPhoneNumbers:[CNLabeledValue<CNPhoneNumber>] = contact.phoneNumbers
let firstPhoneNumber:CNPhoneNumber = userPhoneNumbers[0].value
// user phone number string
let primaryPhoneNumberStr:String = firstPhoneNumber.stringValue
print(primaryPhoneNumberStr)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
}
import ContactsUI
private let contactPicker = CNContactPickerViewController()
Button click that initiates contact picker:
#IBAction func accessContacts(_ sender: Any) {
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
Implement delegate methods
extension YourViewController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let phoneNumberCount = contact.phoneNumbers.count
guard phoneNumberCount > 0 else {
dismiss(animated: true)
//show pop up: "Selected contact does not have a number"
return
}
if phoneNumberCount == 1 {
setNumberFromContact(contactNumber: contact.phoneNumbers[0].value.stringValue)
} else {
let alertController = UIAlertController(title: "Select one of the numbers", message: nil, preferredStyle: .alert)
for i in 0...phoneNumberCount-1 {
let phoneAction = UIAlertAction(title: contact.phoneNumbers[i].value.stringValue, style: .default, handler: {
alert -> Void in
self.setNumberFromContact(contactNumber: contact.phoneNumbers[i].value.stringValue)
})
alertController.addAction(phoneAction)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
dismiss(animated: true)
self.present(alertController, animated: true, completion: nil)
}
}
func setNumberFromContact(contactNumber: String) {
//UPDATE YOUR NUMBER SELECTION LOGIC AND PERFORM ACTION WITH THE SELECTED NUMBER
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
contactNumber = contactNumber.removeWhitespacesInBetween()
guard contactNumber.count >= 10 else {
dismiss(animated: true) {
self.popUpMessageError(value: 10, message: "Selected contact does not have a valid number")
}
return
}
textFieldNumber.text = String(contactNumber.suffix(10))
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
}
You can use this extension to get a contact name.
extension CNContact {
open func displayName() -> String {
return givenName + " " + familyName
}
}
This is Class to get Some details EPContact
Swift 5 & Contact Picker Get Email
I wanted to show an example of how you can do the same thing you asked for but for email instead and updated for Swift 5 since some of the answers do not compile correctly above. This also has the added bonus of the 'all' option which will concatenate multiple emails into one string if the user selects it or not.
First make sure to import import ContactsUI
Then make sure you have an outlet to your textfield.
#IBOutlet var emailTextField: UITextField!
Next you need to set the contact picker as a member variable of your viewController. This will hold the information for showing the contact picker later.
class EmailViewController: UIViewController {
#IBOutlet var emailTextField: UITextField!
private let contactPicker = CNContactPickerViewController()
//... rest of view controller code, etc...
}
Finally you can simply add this extension to the EmailViewController with the code below:
extension EmailViewController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let emailNumberCount = contact.emailAddresses.count
//#JA - They have to have at least 1 email address
guard emailNumberCount > 0 else {
dismiss(animated: true)
//show pop up: "Selected contact does not have a number"
let alertController = UIAlertController(title: "No emails found for contact: "+contact.givenName+" "+contact.familyName, message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Ok", style: .default, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
return
}
//#JA - If they have only 1 email it's easy. If there is many emails we want to concatenate them and separate by commas , , ...
if emailNumberCount == 1 {
setEmailFromContact(contactEmail: contact.emailAddresses[0].value as String)
} else {
let alertController = UIAlertController(title: "Select an email from contact: "+contact.givenName+" "+contact.familyName+" or select 'All' to send to every email listed.", message: nil, preferredStyle: .alert)
for i in 0...emailNumberCount-1 {
let emailAction = UIAlertAction(title: contact.emailAddresses[i].value as String, style: .default, handler: {
alert -> Void in
self.setEmailFromContact(contactEmail: contact.emailAddresses[i].value as String)
})
alertController.addAction(emailAction)
}
let allAction = UIAlertAction(title: "All", style: .destructive, handler: {
alert -> Void in
var emailConcat = ""
for i in 0...emailNumberCount-1{
if(i != emailNumberCount-1){ //#JA - Only add the , if we are not on the last item of the array
emailConcat = emailConcat + (contact.emailAddresses[i].value as String)+","
}else{
emailConcat = emailConcat + (contact.emailAddresses[i].value as String)
}
}
self.setEmailFromContact(contactEmail: emailConcat)//#JA - Sends the concatenated version of the emails separated by commas
})
alertController.addAction(allAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
dismiss(animated: true)
self.present(alertController, animated: true, completion: nil)
}
}
func setEmailFromContact(contactEmail: String){
emailTextField.text = contactEmail
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
print("contact picker canceled")
}
}
To call up the picker in the action event of a button for example you could do this:
#IBAction func contactsButtonPressed(_ sender: UIButton) {
contactPicker.delegate = self
self.present(contactPicker, animated: true, completion: nil)
}
contactPicker.delegate = self works because of the extension on the viewController class (emailViewController) in my case that gives it access to the CNContactPickerDelegate protocol functions it requires.
If contact have more than one phone numbers, then you can get the selected number by comparing the 'contactProperty.identifier' as below -
public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
tvName.text = contactProperty.contact.givenName
var selectedNo = ""
if contactProperty.key == "phoneNumbers" {
contactProperty.contact.phoneNumbers.forEach({ phone in
if phone.identifier == contactProperty.identifier {
selectedNo = phone.value.stringValue
}
})
}
tvContact.text = selectedNo
}

provideAPIKey: should be called at most once (Swift)

I'm using Google maps and places API and i'm trying to load nearby places in a tableView but everytime i come in this class
import UIKit
import MapKit
import CoreLocation
import GoogleMaps
import GooglePlaces
import Social
import AVFoundation
private let resueIdentifier = "MyTableViewCell"
extension UIViewController {
func present(viewController : UIViewController, completion : (() -> ())? = nil ){
if let presented = self.presentedViewController {
presented.dismiss(animated: true, completion: {
self.present(viewController, animated: true, completion: completion)
})
} else {
self.present(viewController, animated: true, completion: completion)
}
}
}
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
struct User {
var name: String
var images: UIImage
var type: String
}
var previuosViewTappedButtonsArray = [String]()
var locationManager:CLLocationManager?
let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
let cellWidth: CGFloat = 250
let radius = 5000 // 5km
var category : QCategoryy?
var currentLocation : CLLocationCoordinate2D?
var places: [QPlace] = []
var isLoading = false
var response : QNearbyPlacesResponse?
var rows = 0
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = category?.name
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
rows = 0
insertRowsMode3()
tableView.reloadData()
category?.markView()
}
#IBAction func refreshTapped(_ sender: Any) {
rows = 0
insertRowsMode3()
tableView.reloadData()
}
func canLoadMore() -> Bool {
if isLoading {
return false
}
if let response = self.response {
if (!response.canLoadMore()) {
return false
}
}
return true
}
func loadPlaces(_ force:Bool) {
if !force {
if !canLoadMore() {
return
}
}
print("load more")
isLoading = true
NearbyPlaces.getNearbyPlaces(by: category?.name ?? "food", coordinates: currentLocation!, radius: radius, token: self.response?.nextPageToken, completion: didReceiveResponse)
}
func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
if let error = error {
let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
})
alertController.addAction(actionRetry)
alertController.addAction(actionDismiss)
DispatchQueue.main.async {
self.present(viewController: alertController)
}
}
if let response = response {
self.response = response
if response.status == "OK" {
if let placesDownloaded = response.places {
places.append(contentsOf: placesDownloaded)
}
self.tableView?.reloadData()
} else {
let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
}))
self.present(viewController: alert)
}
isLoading = false
}
else {
print("response is nil")
}
}
func insertRowsMode2() {
tableView.beginUpdates()
for i in 0..<places.count {
insertRowMode2(ind: i, usr: places[i])
}
tableView.endUpdates()
}
func insertRowMode2(ind:Int,usr:QPlace) {
tableView.beginUpdates()
let indPath = IndexPath(row: ind, section: 0)
rows = ind + 1
tableView.insertRows(at: [indPath], with: .right)
tableView.endUpdates()
}
func insertRowsMode3() {
tableView.beginUpdates()
rows = 0
insertRowMode3(ind: 0)
tableView.endUpdates()
}
func insertRowMode3(ind:Int) {
tableView.beginUpdates()
let indPath = IndexPath(row: ind, section: 0)
rows = ind + 1
tableView.insertRows(at: [indPath], with: .right)
guard ind < places.count-1 else { return }
DispatchQueue.main.asyncAfter(deadline: .now()+0.20) {
self.insertRowMode3(ind: ind+1)
}
tableView.endUpdates()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count /* rows */
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
let place = places[indexPath.row]
cell.update(place: place)
if indexPath.row == places.count - 1 {
loadPlaces(false)
}
/* let user = users[indexPath.row]
cell.selectionStyle = .none
cell.myImage.image = user.images
cell.myLabel.text = user.name
cell.myTypeLabel.text = user.type */
return (cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
UIView.animate(withDuration: 0.2, animations: {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
})
performSegue(withIdentifier: "goToLast" , sender: users[indexPath.row])
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func didReceiveUserLocation(_ userLocation:CLLocation) {
currentLocation = userLocation.coordinate
loadPlaces(true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToLast" && sender is IndexPath {
let dvc = segue.destination as! FinalClass
dvc.index = (sender as! IndexPath).row
dvc.places = places
dvc.userLocation = currentLocation
/* guard let vc = segue.destination as? FinalClass else { return }
let guest = segue.destination as! FinalClass
if let user = sender as? User {
*/
}
}
#IBAction func IndTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func socialShare(_ sender: Any) {
//Alert
let alert = UIAlertController(title: "Share", message: "First share!", preferredStyle: .actionSheet)
//First action
let actionOne = UIAlertAction(title: "Share on Facebook", style: .default) { (action) in
//Checking if user is connected to Facebook
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook)
{
let post = SLComposeViewController(forServiceType: SLServiceTypeFacebook)!
post.setInitialText("First")
post.add(UIImage(named: "uround logo.png"))
self.present(post, animated: true, completion: nil)
} else {self.showAlert(service: "Facebook")}
}
let actionThree = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
//Add action to action sheet
alert.addAction(actionOne)
alert.addAction(actionThree)
//Present alert
self.present(alert, animated: true, completion: nil)
}
func showAlert(service:String)
{
let alert = UIAlertController(title: "Error", message: "You are not connected to \(service)", preferredStyle: .alert)
let action = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
extension CourseClass2: CLLocationManagerDelegate {
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
didReceiveUserLocation(userLocation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
errorGettingCurrentLocation(error.localizedDescription)
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
locationManager?.startUpdatingLocation()
//locationManager.startUpdatingHeading()
} else if status == .denied || status == .restricted {
errorGettingCurrentLocation("Location access denied")
}
}
func errorGettingCurrentLocation(_ errorMessage:String) {
let alert = UIAlertController.init(title: "Error", message: errorMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
i get the message "error - response status" from this function
func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
if let error = error {
let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
})
alertController.addAction(actionRetry)
alertController.addAction(actionDismiss)
DispatchQueue.main.async {
self.present(viewController: alertController)
}
}
if let response = response {
self.response = response
if response.status == "OK" {
if let placesDownloaded = response.places {
places.append(contentsOf: placesDownloaded)
}
self.tableView?.reloadData()
} else {
let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
}))
self.present(viewController: alert)
}
isLoading = false
}
else {
print("response is nil")
}
}
so looking in the console i saw this error "((null)) was false: provideAPIKey: should be called at most once" which is perhaps the cause of the problem (even if I'm not sure), i followed the google documentation guide to get the API key for the project, here is my appDelegate where there are my keys (i changed for now the numbers of the key with "My Api key")
import UIKit
import Firebase
import CoreLocation
import GoogleMaps
import GooglePlaces
import FBSDKCoreKit
import GoogleSignIn
import FBSDKShareKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
static let googleMapsApiKey = "MY API Key"
static let googlePlacesAPIKey = "MY API Key"
var window: UIWindow?
var locationManager: CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
locationManager = CLLocationManager()
locationManager?.requestWhenInUseAuthorization()
GMSServices.provideAPIKey(AppDelegate.googleMapsApiKey)
GMSPlacesClient.provideAPIKey(AppDelegate.googlePlacesAPIKey)
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
IQKeyboardManager.sharedManager().enable = true
IQKeyboardManager.sharedManager().enableAutoToolbar = false
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
if GMSServices.provideAPIKey("MY API Key") {
print("good provided keys correctly")
}
else {
print("key didn't provided")
}
return true
}
someone can tell if the problem is a wrong use of the api key or if the keys are wrong or maybe the problem is another ?
Look at what you are doing here:
// once
GMSServices.provideAPIKey(AppDelegate.googleMapsApiKey)
GMSPlacesClient.provideAPIKey(AppDelegate.googlePlacesAPIKey)
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
IQKeyboardManager.sharedManager().enable = true
IQKeyboardManager.sharedManager().enableAutoToolbar = false
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
// twice!
if GMSServices.provideAPIKey("MY API Key") {
You are calling provideAPIKey twice!
I know you want to check whether the API key is provided correctly, but the correct way to do this is not to call the method twice. Instead, you should store the return value and check the return value:
// put the return value in "success"
let success = GMSServices.provideAPIKey(AppDelegate.googleMapsApiKey)
GMSPlacesClient.provideAPIKey(AppDelegate.googlePlacesAPIKey)
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
IQKeyboardManager.sharedManager().enable = true
IQKeyboardManager.sharedManager().enableAutoToolbar = false
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
// check "success"
if success {

How can I display a popup message in Swift that disappears after 3 seconds or can be cancelled by user immediatelly?

In my swift app I have a UIViewController with a single button.
This button invokes a function that calls a popup that disappears after 3 seconds. Also, after that time it prints a message to the console. The code of this function is as follows:
func showAlertMsg(title: String, message: String){
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
self.presentViewController(alertController, animated: true, completion: nil)
let delay = 3.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
alertController.dismissViewControllerAnimated(true, completion: nil)
print("popup disappeared")
})
}
That works fine, but I wanted to introduce some improvement. I wanted to add there a button that will cancel this popup immediately and then avoid displaying the message in the console. Is there a way of displaying such popup to the user? Also - is there a way of showing in this popup message the counter with number of seconds running out that shows how much time is left until the popup disappears?
You can use an NSTimer to decrement a counter, update the alert view and dismiss the alert view when the counter reaches 0. This code is adapted from my Objective-C answer
class ViewController: UIViewController {
var alertController: UIAlertController?
var alertTimer: NSTimer?
var remainingTime = 0
var baseMessage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.showAlertMsg("Test Alert", message: "This will disappear in ", time: 5)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showAlertMsg(title: String, message: String, time: Int) {
guard (self.alertController == nil) else {
print("Alert already displayed")
return
}
self.baseMessage = message
self.remainingTime = time
self.alertController = UIAlertController(title: title, message: self.alertMessage(), preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
print("Alert was cancelled")
self.alertController=nil;
self.alertTimer?.invalidate()
self.alertTimer=nil
}
self.alertController!.addAction(cancelAction)
self.alertTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(ViewController.countDown), userInfo: nil, repeats: true)
self.presentViewController(self.alertController!, animated: true, completion: nil)
}
func countDown() {
self.remainingTime -= 1
if (self.remainingTime < 0) {
self.alertTimer?.invalidate()
self.alertTimer = nil
self.alertController!.dismissViewControllerAnimated(true, completion: {
self.alertController = nil
})
} else {
self.alertController!.message = self.alertMessage()
}
}
func alertMessage() -> String {
var message=""
if let baseMessage=self.baseMessage {
message=baseMessage+" "
}
return(message+"\(self.remainingTime)")
}
}
Just in case someone needs it, this is a Swift 4 version of the #Paulw11 solution
import UIKit
class ViewController: UIViewController {
var alertController: UIAlertController?
var alertTimer: Timer?
var remainingTime = 0
var baseMessage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.showAlertMsg(title: "Test Alert", message: "This will disappear in ", time: 5)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showAlertMsg(title: String, message: String, time: Int) {
guard (self.alertController == nil) else {
print("Alert already displayed")
return
}
self.baseMessage = message
self.remainingTime = time
self.alertController = UIAlertController(title: title, message: self.alertMessage(), preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
print("Alert was cancelled")
self.alertController=nil;
self.alertTimer?.invalidate()
self.alertTimer=nil
}
self.alertController!.addAction(cancelAction)
self.alertTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.countDown), userInfo: nil, repeats: true)
self.present(self.alertController!, animated: true, completion: nil)
}
#objc func countDown() {
self.remainingTime -= 1
if (self.remainingTime < 0) {
self.alertTimer?.invalidate()
self.alertTimer = nil
self.alertController!.dismiss(animated: true, completion: {
self.alertController = nil
})
} else {
self.alertController!.message = self.alertMessage()
}
}
func alertMessage() -> String {
var message=""
if let baseMessage=self.baseMessage {
message=baseMessage+" "
}
return(message+"\(self.remainingTime)")
}
}
I know this directly doesn't answer your question, but have you considered using MBProgressHUD SCLAlertView? They both offer functions that allow you to display an alert that disappears after a set amount of time. SCLAlertView allows the user to cancel immediately where as MBProgressHUD does not. If you want more info on how to implement these, let me know so I can add more info!

UIAlertView not returning appropriate value

Here is my code
import Foundation
import UIKit
class Alert: UIViewController {
class func showAlert(title: String, message: String, buttonTitle: String? = nil, buttonTitle2: String? = nil, sender: UIViewController) -> Int? {
var flag : Int?
func yes(){
flag = 1
}
func no(){
flag = 0
}
//= nil ;
if objc_getClass("UIAlertController") != nil {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
if (buttonTitle != nil){
alert.addAction(UIAlertAction(title: buttonTitle, style: UIAlertActionStyle.Default, handler: { (action: UIAlertAction!) in
yes()
}))
}
if (buttonTitle2 != nil) {
alert.addAction(UIAlertAction(title: buttonTitle2, style: UIAlertActionStyle.Default, handler: { (action: UIAlertAction!) in
no()
}))
}
sender.presentViewController(alert, animated: true, completion: nil)
return flag
} else {
let alert = UIAlertView()
alert.title = title
alert.message = message
alert.addButtonWithTitle(buttonTitle)
alert.addButtonWithTitle(buttonTitle2)
print("inside Else")
alert.show()
return flag
//make and use a UIAlertView
}
}
}
The problem is that the flag getting returned is always nil to the view controller I call Alert from. I need the flag to return 1 if "yes" is pressed and 0 if "No" is pressed. I have been stuck in this problem for lot of time. Help.
Try this code,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func showAlert() {
let alertController = UIAlertController(title: "Hey there", message: "Do you want to do it?", preferredStyle: .Alert)
let btnAction1 = UIAlertAction(title: "Yes", style: .Default) { (action : UIAlertAction) -> Void in
self.yes()
}
let btnAction2 = UIAlertAction(title: "No", style: .Default) { (action : UIAlertAction) -> Void in
self.no()
}
alertController.addAction(btnAction1)
alertController.addAction(btnAction2)
presentViewController(alertController, animated: true, completion: nil)
}
func yes() -> Int
{
print("Yes")
return 1
}
func no() -> Int
{
print("No")
return 0
}
}
Its working fine here, i hope it will work for u also.. :)
You made a mistake that closure won't execute until button clicked but function will return right now.You should deliver a closure to this function.It will look like:
class func showAlert(title: String, message: String, buttonTitle: String? = nil, buttonTitle2: String? = nil, sender: UIViewController,handler: ((Bool) -> Void)) -> Void
And handle it in alert action like this:
handler(true)

Resources