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
}
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 need to use UIAlertContoller, as SwiftUI's Alert does not support TextField.
I must not use Custom created AlertView, due to various reason(Accessibility, DynamicType, Dark Mode support etc).
Basic Idea is, SwiftUI's alert must hold TextField & entered text must be reflect back for usage.
I created a SwiftUI view by Conforming to UIViewControllerRepresentable following is working code.
struct AlertControl: UIViewControllerRepresentable {
typealias UIViewControllerType = UIAlertController
#Binding var textString: String
#Binding var show: Bool
var title: String
var message: String
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertControl>) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = "Enter some text"
}
let cancelAction = UIAlertAction(title: "cancel", style: .destructive) { (action) in
self.show = false
}
let submitAction = UIAlertAction(title: "Submit", style: .default) { (action) in
self.show = false
}
alert.addAction(cancelAction)
alert.addAction(submitAction)
return alert
}
func updateUIViewController(_ uiViewController: UIAlertController, context: UIViewControllerRepresentableContext<AlertControl>) {
}
func makeCoordinator() -> AlertControl.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var control: AlertControl
init(_ control: AlertControl) {
self.control = control
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text {
self.control.textString = text
}
return true
}
}
}
// SwiftUI View in some content view
AlertControl(textString: self.$text,
show: self.$showAlert,
title: "Title goes here",
message: "Message goes here")
Problem:
There is No activity in Alert Action when it is tapped. I put breakpoints to check, but it never hit there.
Even UITextFieldDelegate's function never hit.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
Edit: cancelAction or submitAction does not triggers on tap of these fields.
Here is full demo module for solution that works. Tested with Xcode 11.4 / iOS 13.4
See also important comments inline
struct AlertControl: UIViewControllerRepresentable {
#Binding var textString: String
#Binding var show: Bool
var title: String
var message: String
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertControl>) -> UIViewController {
return UIViewController() // holder controller - required to present alert
}
func updateUIViewController(_ viewController: UIViewController, context: UIViewControllerRepresentableContext<AlertControl>) {
guard context.coordinator.alert == nil else { return }
if self.show {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
context.coordinator.alert = alert
alert.addTextField { textField in
textField.placeholder = "Enter some text"
textField.text = self.textString // << initial value if any
textField.delegate = context.coordinator // << use coordinator as delegate
}
alert.addAction(UIAlertAction(title: "cancel", style: .destructive) { _ in
// your action here
})
alert.addAction(UIAlertAction(title: "Submit", style: .default) { _ in
// your action here
})
DispatchQueue.main.async { // must be async !!
viewController.present(alert, animated: true, completion: {
self.show = false // hide holder after alert dismiss
context.coordinator.alert = nil
})
}
}
}
func makeCoordinator() -> AlertControl.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var alert: UIAlertController?
var control: AlertControl
init(_ control: AlertControl) {
self.control = control
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
self.control.textString = text.replacingCharacters(in: range, with: string)
} else {
self.control.textString = ""
}
return true
}
}
}
// Demo view for Alert Controll
struct DemoAlertControl: View {
#State private var text = ""
#State private var showAlert = false
var body: some View {
VStack {
Button("Alert") { self.showAlert = true }
.background(AlertControl(textString: self.$text, show: self.$showAlert,
title: "Title goes here", message: "Message goes here"))
Text(self.text)
}
}
}
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.
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))
}
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
}