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))
}
Related
I want to use an alert with textField in my swiftUI app so I made following wrapper for UIAlertController. I want to disable my save button when there is no text in textField and enable it the moment user enters some text, but for some reason it is not happening, the button is stuck to the initial state.
code
struct UIAlertControllerWrapper: UIViewControllerRepresentable {
#Binding var text: String
#Binding var showAlert: Bool
var title: String
var message: String
var placeholder: String
var action: () -> Void
func makeUIViewController(context: Context) -> some UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
if showAlert {
let alert = createAlert(context)
context.coordinator.alert = alert
alert.actions[1].isEnabled = false
DispatchQueue.main.async {
uiViewController.present(alert, animated: true) {
showAlert = false
}
}
}
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: UIAlertControllerWrapper
var alert: UIAlertController?
init(_ parent: UIAlertControllerWrapper) {
self.parent = parent
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as? NSString {
parent.text = text.replacingCharacters(in: range, with: string)
} else {
parent.text = ""
}
guard let alert = alert else {
return true
}
if parent.text.trimmingCharacters(in: .whitespaces).isEmpty {
alert.actions[1].isEnabled = false
} else {
alert.actions[1].isEnabled = true
}
return true
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func createAlert(_ context: Context) -> UIAlertController {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert
)
alert.addTextField { textField in
textField.placeholder = placeholder
textField.text = text
textField.delegate = context.coordinator
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
alert.dismiss(animated: true) {
showAlert = false
}
text = ""
}
let save = UIAlertAction(title: "Save", style: .default) { _ in
action()
alert.dismiss(animated: true) {
showAlert = false
}
text = ""
}
alert.addAction(cancel)
alert.addAction(save)
return alert
}
}
I'm working on a project titled "apple pie" and I'm trying to figure out if there is a way for a user to enter their name on a pop-up alert, I coded, and for that name, that they typed, to appear or re-write a blue background label on the game.
This is all the code I have so far:
import UIKit
class ViewController: UIViewController {
#IBOutlet var treeImageView: UIImageView!
#IBOutlet var correctWordLabel: UILabel!
#IBOutlet var scoreLabel: UILabel!
#IBOutlet var letterButtons: [UIButton]!
//Player name output to blue label
#IBOutlet weak var textfield: UILabel!
//Click to enter player name
#IBAction func enterButton(_ sender: Any) {
// textfield.text = textbox.text
showNamePopup()
}
var listOfWords = ["mistery", "stars", "banana", "igloo", "bug", "programming", "car", "motorcycle", "weasel", "cat", "bazzinga"]
let incorrectMovesAllowed = 7
var totalWins = 0 {
didSet {
newRound()
}
}
var totalLosses = 0 {
didSet {
newRound()
}
}
var currentGame: Game!
override func viewDidLoad() {
super.viewDidLoad()
newRound()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func newRound() {
if !listOfWords.isEmpty {
let newWord = listOfWords.removeFirst()
currentGame = Game(word: newWord, incorrectMovesRemaining: incorrectMovesAllowed, guessedLetters: [])
enableLetterButtons(true)
updateUI()
} else {
enableLetterButtons(false)
}
}
func enableLetterButtons(_ enable: Bool) {
for button in letterButtons {
button.isEnabled = enable
}
}
func updateUI() {
var letters = [String]()
for letter in currentGame.formattedWord {
letters.append(String(letter))
}
let wordWithSpacing = letters.joined(separator: " ")
correctWordLabel.text = wordWithSpacing
scoreLabel.text = "Wins: \(totalWins), Losses: \(totalLosses)"
treeImageView.image = UIImage(named: "Tree \(currentGame.incorrectMovesRemaining)")
}
#IBAction func letterButtonPressed(_ sender: UIButton) {
sender.isEnabled = false
let letterString = sender.title(for: .normal)!
let letter = Character(letterString.lowercased())
currentGame.playerGuessed(letter: letter)
updateGameState()
}
func updateGameState() {
if currentGame.incorrectMovesRemaining == 0 {
totalLosses += 1
} else if currentGame.word == currentGame.formattedWord {
totalWins += 1
} else {
updateUI()
}
}
/// Pop-Up Coding
private func showNamePopup(){
// Make a confirmation alert dialog.
let alert = UIAlertController(
title: "Enter Name",
message: "Enter player name if you wish to save progess",
preferredStyle: .alert
)
// Add a text field to get user name.
alert.addTextField { textField in _ = textField.text}
// Add cancel action.
alert.addAction(UIAlertAction(title: "Cancel",style: .destructive, handler: { _ in} ))
// Handle state if user cancels the pop-up.
// Add ok action.
alert.addAction(UIAlertAction(title: "Ok",style: .default))
// Show pop-up. Add a completion when pop-up is destroyed.
self.present(alert, animated: true, completion: nil );
}
}
A pop-up of user input:
Apple pie game:
You need to add a completion handler to the 'OK' action in your alert.
private func showNamePopup(){
// Make a confirmation alert dialog.
let alert = UIAlertController(
title: "Enter Name",
message: "Enter player name if you wish to save progess",
preferredStyle: .alert
)
// Add a text field to get user name.
alert.addTextField { textField in _ = textField.text}
// Add cancel action.
alert.addAction(UIAlertAction(title: "Cancel",style: .destructive, handler: { _ in} ))
// Handle state if user cancels the pop-up.
// Add ok action.
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in
if let name = alert.textFields?.first?.text {
self?.nameLabel.text = name // Show the name in the text label
}
}
alert.addAction(okAction)
// Show pop-up. Add a completion when pop-up is destroyed.
self.present(alert, animated: true, completion: nil );
}
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.
How can I check if the value from an UITextField is an interval of numbers between 1 - 100 and IF the number IS in that interval to send the value to another UIViewController? If the value in not in that interval then to show an alert.
My other controller have a var receivedValue = "" which I will use it to populate a UILabel.
Here is my code:
import UIKit
class ChildViewController: UIViewController {
#IBOutlet weak var insertNumberTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
insertNumberTextField.delegate = self
}
#IBAction func checkNumberIntervalButton(_ sender: UIButton) {
if insertNumberTextField.text == "\(1..<100)"{
print("Number is in interval 1 - 100.")
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "Try again", message: "Sorry but this numer is not in the inverval 1 - 100. Try again.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
insertNumberTextField.text = ""
print("Number is not in the inverval 1 - 100.")
}
}
}
extension ChildViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
}
Convert the text field's text into an Int and compare it that way.
if let text = insertNumberTextField.text, let value = Int(text), (0 ..< 100).contains(value) {
// number is between 1 and 100
} else {
// show alert
}
PFCloud.callFunctionInBackground("updatePositions", withParameters: ["username" : username, "location" : locationTitle, "instructor" : instructorSwitch.on, "guard" : guardSwitch.on, "sup" : supSwitch.on]) {
(positions: AnyObject!, error: NSError!) -> Void in
if error == nil {
self.currentUser.setValue(self.instructorSwitch.on, forKey: (self.shortTitle + "Instructor"))
self.currentUser.setValue(self.guardSwitch.on, forKey: (self.shortTitle + "Guard"))
self.currentUser.setValue(self.supSwitch.on, forKey: (self.shortTitle + "Sup"))
self.currentUser.save(nil)
self.navigationController?.popToRootViewControllerAnimated(true)
}
else {
let errorAlert = UIAlertController (title: "Error", message: "There was an error while processing your request. This may be because the app could not connect to the internet. Please try again.", preferredStyle: UIAlertControllerStyle.Alert)
let actionCancel = UIAlertAction (title: "Dismiss", style: .Cancel, handler: nil)
errorAlert.addAction(actionCancel)
self.presentViewController(errorAlert, animated: true, completion: nil)
}
When I run the above code, my goal to is to update the Parse Objects in my Data Browser and at the same time edit the same objects in currentUser.
As it stands now, the Login VC is shown if no one is logged in (based on a boolean value in Core Data). When a user logs in they are taken to the apps Home Page (and the boolean value is changed). If they were to shutdown the app and restart, they would be taken to the Home Page rather than the Login VC. I have a "dummy" VC where the Core Data entity is updated (I know it sounds redundant but it's there for a purpose that is unrelated to my issue).
Login VC
#IBOutlet var fullName: UITextField!
#IBOutlet var password: UITextField!
#IBOutlet var helpButton: UIButton!
#IBOutlet var loginButton: UIButton!
var nameID: String!
var passwordStatus: Bool?
var username: String?
var userID: String?
var objectID: String!
var currentUser = PFUser.currentUser()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
checkInternetConnection()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true)
}
#IBAction func LoginTapped(sender: AnyObject) {
if (fullName.text.isEmpty || password.text.isEmpty) {
let alert = UIAlertView()
alert.title = "Error"
alert.message = "You have not completed the required fields!"
alert.addButtonWithTitle("Dismiss")
alert.show()
}
else {
PFUser.logInWithUsernameInBackground(fullName.text, password: password.text) {
(user: PFUser!, error: NSError!) -> Void in
if (user != nil) {
self.currentUser = user
self.currentUser.save()
//println(self.currentUser.objectForKey("username") as String!)
if (self.currentUser.objectForKey("passwordChanged") as Bool! == nil || self.currentUser.objectForKey("passwordChanged") as Bool! == false) {
self.performSegueWithIdentifier("password", sender: self)
}
else {
self.performSegueWithIdentifier("skipPassword", sender: self)
}
}
else {
let errorAlert = UIAlertController (title: "Error", message: "Profile not found. Please check your username and password.", preferredStyle: UIAlertControllerStyle.Alert)
let actionCancel = UIAlertAction (title: "Dismiss", style: .Cancel, handler: nil)
errorAlert.addAction(actionCancel)
self.presentViewController(errorAlert, animated: true, completion: nil)
}
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "password") {
var svc = segue.destinationViewController as AccountSetup
svc.username = fullName.text
}
else if (segue.identifier == "skipPassword") {
var svc = segue.destinationViewController as Agree
svc.password = password.text
}
}
The only place that there is a chance for the user to log out is in the app's Settings controller:
Settings VC
var choice: String!
var currentEmail: String!
var sendEmail: String!
var email: UITextField!
var listArray = NSArray (objects: "-", "-", "-", "1", "2", "3", "4", "5", "6")
var currentUser = PFUser.currentUser()
var res : NSManagedObject!
var context : NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
currentEmail = currentUser.objectForKey("email") as String!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 4
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
switch(section) {
case 0: return 2
case 1: return 9
case 2: return 4
case 3: return 1
default: fatalError("Unknown number of sections")
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
choice = listArray.objectAtIndex(indexPath.row) as NSString
if (indexPath.row >= 3) {
performSegueWithIdentifier("positions", sender: self)
}
else if (indexPath.row == 1) {
var passwordAlert = UIAlertController (title: "Change Password", message: "For security reasons, you can not change your password through the app. We will send an email to " + currentEmail + ". If you rather us send it to another email, enter it below. Otherwise, click 'Send'.", preferredStyle: UIAlertControllerStyle.Alert)
let standardCancel = UIAlertAction (title: "Dismiss", style: .Cancel, handler: nil)
let actionSubmit = UIAlertAction (title: "Send", style: .Default) { (action) in
if (self.email.text.isEmpty) {
self.sendEmail = self.currentEmail
}
else {
self.sendEmail = self.email.text
}
PFUser.requestPasswordResetForEmailInBackground(self.sendEmail){
(success: Bool!, error: NSError!) -> Void in
if (success == true) {
let emailAlert = UIAlertController (title: "Password", message: "An email containing information on how to change your password has been sent to " + self.sendEmail + ".", preferredStyle: UIAlertControllerStyle.Alert)
emailAlert.addAction(standardCancel)
self.presentViewController(emailAlert, animated: true, completion: nil)
}
else {
let errorAlert = UIAlertController (title: "Error", message: "There was an error while processing your request. This may be because the email is invalid or the app could not connect to the internet. Please try again.", preferredStyle: UIAlertControllerStyle.Alert)
errorAlert.addAction(standardCancel)
self.presentViewController(errorAlert, animated: true, completion: nil)
}
}
}
let actionCancel = UIAlertAction (title: "Cancel", style: .Cancel, handler: nil)
passwordAlert.addTextFieldWithConfigurationHandler {
(textField) in textField.placeholder = "Email"
self.email = textField
}
passwordAlert.addAction(actionSubmit)
passwordAlert.addAction(actionCancel)
self.presentViewController(passwordAlert, animated: true, completion: nil)
}
}
#IBAction func logoutPressed (sender: AnyObject) {
PFUser.logOut()
//code to change Core Data boolean value to start on Login is here but there is no need to put it (unrelated)
tabBarController?.tabBar.hidden = true
performSegueWithIdentifier("goToLogin", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "positions") {
var svc = segue.destinationViewController as UpdatePositions;
svc.locationTitle = choice
println (choice)
}
}
Like I've mentioned before, everything works up until the app is shutdown where the authentication is lost.
Ends up that there was an error in the 1.6.0 Parse SDK. Works fine with 1.6.5.