How to limit character for username in Swift - swift

i need to limit character for username in my swift code.
Username only can use those characters "abcdefghijklmnopqrstuvwxyz._1234567890".
Please forgive me if i'm noob, i don't have programming background. Still learning .
Below are swift code, which part i need to edit ?
// MARK: - SIGNUP BUTTON
#IBAction func signupButt(_ sender: AnyObject) {
dismissKeyboard()
// You acepted the TOS
if tosAccepted {
if usernameTxt.text == "" || passwordTxt.text == "" || emailTxt.text == "" || fullnameTxt.text == "" {
simpleAlert("You must fill all fields to sign up on \(APP_NAME)")
self.hideHUD()
} else {
showHUD("Please wait...")
let userForSignUp = PFUser()
userForSignUp.username = usernameTxt.text!.lowercased()
userForSignUp.password = passwordTxt.text
userForSignUp.email = emailTxt.text
userForSignUp[USER_FULLNAME] = fullnameTxt.text
userForSignUp[USER_IS_REPORTED] = false
let hasBlocked = [String]()
userForSignUp[USER_HAS_BLOCKED] = hasBlocked
// Save Avatar
let imageData = avatarImg.image!.jpegData(compressionQuality: 1.0)
let imageFile = PFFile(name:"avatar.jpg", data:imageData!)
userForSignUp[USER_AVATAR] = imageFile
userForSignUp.signUpInBackground { (succeeded, error) -> Void in
if error == nil {
self.hideHUD()
let alert = UIAlertController(title: APP_NAME,
message: "We have sent you an email that contains a link - you must click this link to verify your email and go back here to login.",
preferredStyle: .alert)
// Logout and Go back to Login screen
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
PFUser.logOutInBackground(block: { (error) in
self.dismiss(animated: false, completion: nil)
})
})
alert.addAction(ok)
self.present(alert, animated: true, completion: nil)
// ERROR
} else {
self.simpleAlert("\(error!.localizedDescription)")
self.hideHUD()
}}
}

You can use regex for this, check out the code below.
let usernameRegex = "^[a-zA-Z0-9]{4,10}$"
let usernameTest = NSPredicate(format:"SELF MATCHES %#", usernameRegex)
print(usernameTest.evaluate(with: "asAZ")) // boolen
You can even create an extension out of it like this
extension String {
func isValidUserName() -> Bool{
let usernameRegex = "^[a-zA-Z0-9]{4,10}$" // your regex
let usernameTest = NSPredicate(format:"SELF MATCHES %#", usernameRegex)
return usernameTest.evaluate(with: self)
}
}
use it like this
yourText.isValidUserName() // return true or false .
You can google any kind of regex to fit your case, and future ones, i even recommend saving those regex in an enum, and create a function that accept those enums and validate, look at this as a hint
enum ValidationRgex: String {
case username = "^[a-zA-Z0-9]{4,10}$"
}
extension String {
func isValid(_ regex: ValidationRgex) -> Bool{
let usernameRegex = regex.rawValue
let usernameTest = NSPredicate(format:"SELF MATCHES %#", usernameRegex)
return usernameTest.evaluate(with: self)
}
}
"MyText".isValid(.username) // usage

Related

How to Read Data from Text File iOS 15 [duplicate]

This question already has answers here:
UIDocumentPickerViewController is not working when testing with my iPad but fine with simulators
(2 answers)
Closed 12 months ago.
Update: This code works in the simulator, but not on my device. Obviously, I'm needing it to work on both.
I've followed the tutorials, yet I cannot seem to get this feature to work. When the user selects the barButtonItem, DocumentPicker opens allowing the user to select a .txt file. I then take the URL to the selected file and attempt to return a string from it; however, I'm getting the following error: "The file “Test.txt” couldn’t be opened because you don’t have permission to view it." What am I missing? Did I fail to ask for permission somewhere? I've tried cleaning the build folder - didn't work.
#IBAction func importFileBtnTapped(_ sender: Any) {
selectFiles()
}
func selectFiles() {
let types = UTType.types(tag: "txt",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let myURL = urls.first else {
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to retrieve document.")
return
}
let text = createStringFromSelectedFile(fileURL: myURL)
if text == "error" {
print("ERROR creating a string from the selected file.")
return
}
let separatedStrings = decipherString(text: text)
if separatedStrings.first == "error" {
print("ERROR deciphering the string in ClaimInfoViewController")
return
}
for string in separatedStrings {
print("\(string)")
}
print("import result: \(myURL)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func createStringFromSelectedFile(fileURL: URL) -> String {
var text = String()
do {
text = try String(contentsOf: fileURL)
}
catch {
print("ERROR in the createStringFromSelectedFile function in ClaimInfoViewController")
print("The error: \(error.localizedDescription)")
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to read the file. Please try again.")
return "error"
}
return text
}
func decipherString(text: String) -> [String]{
let newText = text
let startIndexes = ["<Claim#",
"<File#",
"<DateOfLoss:"
]
var claimNumber = String()
var fileNumber = String()
var dateOfLoss = String()
for indexValue in startIndexes {
guard let index = newText.firstIndex(of: ">") else { return ["error"] }
let newString = String(newText[..<index])
if indexValue == "<Claim#" {
claimNumber = newString
}
else if indexValue == "<File#" {
fileNumber = newString
}
else if indexValue == "<DateOfLoss:" {
dateOfLoss = newString
}
}
let finalText = [claimNumber, fileNumber, dateOfLoss]
return finalText
}
Thanks to matt, who commented above, I was able to find out that it's a security issue. Adding this simple code resolved the issue:
let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
I added it right before this line of code that can be seen above:
let text = createStringFromSelectedFile(fileURL: myURL)
I got this code from here: StackOverflow Post

Enable copy button title on long press on the button

I have UIButton for an address in my tableview cell. When I tap on it once; I open google map with the direction no problem. Now, I want to provide the option for long gesture so if you hold your finger on the button, it provides the option to copy the address which is in the title of the button. This is my code:
#IBOutlet weak var addressBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
addLongPressGesture()
}
#objc func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizer.State.began {
// how do I make it possible to copy the title of the button here? The address is already inserted as the title of the button
}
}
func addLongPressGesture(){
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gesture:)))
longPress.minimumPressDuration = 0.5
self.addressBtn.addGestureRecognizer(longPress)
}
This is where on one tap it goes to the map with no problem; so I have no issue here but just fyi:
#IBAction func addressClicked(_ sender: Any) {
if (UIApplication.shared.canOpenURL(NSURL(string:"comgooglemaps://")! as URL)) {
let street = order.street.replacingOccurrences(of: " ", with: "+")
let postalCode = order.postalCode.replacingOccurrences(of: " ", with: "+")
if street == "" || order.city == "" || order.province == "" || postalCode == ""{
UIApplication.shared.open(URL(string:"comgooglemaps://?saddr=&daddr=\(order.longitude),\(order.latitude)&directionsmode=driving")! as URL)
} else {
UIApplication.shared.open(URL(string:"comgooglemaps://?saddr=&daddr=+\(street),+\(order.city),+\(order.province),+\(postalCode)&directionsmode=driving")! as URL)
}
} else {
NSLog("Can't use comgooglemaps://")
}
}
Use
let text = addressBtn.currentTitle
or
let text = addressBtn.titleLabel?.text
I figured it out with the following code:
//Create the AlertController and add Its action like button in Actionsheet
let actionSheetControllerIOS8: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheetControllerIOS8.view.tintColor = AppColors.Blue
let cancelActionButton = UIAlertAction(title: "Cancel", style: .cancel) { _ in
}
actionSheetControllerIOS8.addAction(cancelActionButton)
let saveActionButton = UIAlertAction(title: "Open Google Map", style: .default)
{ _ in
if (UIApplication.shared.canOpenURL(NSURL(string:"comgooglemaps://")! as URL)) {
let street = order.street.replacingOccurrences(of: " ", with: "+")
let postalCode = order.postalCode.replacingOccurrences(of: " ", with: "+")
if street == "" || order.city == "" || order.province == "" || postalCode == ""{
UIApplication.shared.open(URL(string:"comgooglemaps://?saddr=&daddr=\(order.longitude),\(order.latitude)&directionsmode=driving")! as URL)
} else {
UIApplication.shared.open(URL(string:"comgooglemaps://?saddr=&daddr=+\(street),+\(order.city),+\(order.province),+\(postalCode)&directionsmode=driving")! as URL)
}
} else {
NSLog("Can't use comgooglemaps://")
}
}
actionSheetControllerIOS8.addAction(saveActionButton)
let deleteActionButton = UIAlertAction(title: "Copy Address", style: .default)
{ _ in
let address = "\(order.street), \(order.city), \(order.province), \(order.postalCode)"
let pasteBoard = UIPasteboard.general
pasteBoard.string = address
}
actionSheetControllerIOS8.addAction(deleteActionButton)
self.present(actionSheetControllerIOS8, animated: true, completion: nil)
}

Why does my segue not wait until completion handler finished?

I have a page based app, using RootViewController, ModelViewController, DataViewController, and a SearchViewController.
In my searchViewController, I search for an item and then add or remove that Item to an array which is contained in a Manager class(and UserDefaults), which the modelViewController uses to instantiate an instance of DataViewController with the correct information loaded using the dataObject. Depending on whether an Item was added or removed, I use a Bool to determine which segue was used, addCoin or removeCoin, so that the RootViewController(PageView) will show either the last page in the array, (when a page is added) or the first (when removed).
Everything was working fine until I ran into an error which I can not diagnose, the problem is that when I add a page, the app crashes, giving me a "unexpectadely found nil when unwrapping an optional value"
This appears to be the problem function, in the searchViewController 'self.performSegue(withIdentifier: "addCoin"' seems to be called instantly, even without the dispatchque:
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
CGPrices.shared.getData(arr: true, completion: { (success) in
print(Manager.shared.coins)
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
})
}
searchBar.text = ""
}
Meaning that In my DataViewController, this function will find nil:
func getIndex() {
let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
dataIndex = index
}
I can't find out why it does not wait for completion.
I also get this error about threads:
[Assert] Cannot be called with asCopy = NO on non-main thread.
which is why I try to do the push segue using dispatch que
Here is my searchViewController full code:
import UIKit
class SearchViewController: UIViewController, UISearchBarDelegate {
let selectionLabel = UILabel()
let searchBar = UISearchBar()
let addButton = UIButton()
let removeButton = UIButton()
var filteredObject: [String] = []
var dataObject = ""
var isSearching = false
//Add Button Action.
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
CGPrices.shared.getData(arr: true, completion: { (success) in
print(Manager.shared.coins)
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
})
}
searchBar.text = ""
}
//Remove button action.
#objc func removeButtonActon(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.removeCoin(coin: dataObject)
self.performSegue(withIdentifier: "addCoin", sender: self)
}
searchBar.text = ""
}
//Prepare for segue, pass removeCoinSegue Bool depending on remove or addCoin.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addCoin" {
if let destinationVC = segue.destination as? RootViewController {
destinationVC.addCoinSegue = true
}
} else if segue.identifier == "addCoin" {
if let destinationVC = segue.destination as? RootViewController {
destinationVC.addCoinSegue = false
}
}
}
//Remove button action.
#objc func removeButtonAction(sender: UIButton!) {
if Manager.shared.coins.count == 1 {
removeAlert()
} else {
Manager.shared.removeCoin(coin: dataObject)
print(Manager.shared.coins)
print(dataObject)
searchBar.text = ""
self.removeButton.isHidden = true
DispatchQueue.main.async {
self.performSegue(withIdentifier: "removeCoin", sender: self)
}
}
}
//Search/Filter the struct from CGNames, display both the Symbol and the Name but use the ID as dataObject.
func filterStructForSearchText(searchText: String, scope: String = "All") {
if !searchText.isEmpty {
isSearching = true
filteredObject = CGNames.shared.coinNameData.filter {
// if you need to search key and value and include partial matches
// $0.key.contains(searchText) || $0.value.contains(searchText)
// if you need to search caseInsensitively key and value and include partial matches
$0.name.range(of: searchText, options: .caseInsensitive) != nil || $0.symbol.range(of: searchText, options: .caseInsensitive) != nil
}
.map{ $0.id }
} else {
isSearching = false
print("NoText")
}
}
//Running filter function when text changes.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterStructForSearchText(searchText: searchText)
if isSearching == true && filteredObject.count > 0 {
addButton.isHidden = false
dataObject = filteredObject[0]
selectionLabel.text = dataObject
if Manager.shared.coins.contains(dataObject) {
removeButton.isHidden = false
addButton.isHidden = true
} else {
removeButton.isHidden = true
addButton.isHidden = false
}
} else {
addButton.isHidden = true
removeButton.isHidden = true
selectionLabel.text = "e.g. btc/bitcoin"
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Setup the UI.
self.view.backgroundColor = .gray
setupView()
}
override func viewDidLayoutSubviews() {
}
//Hide keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//Alerts
func removeAlert() {
let alertController = UIAlertController(title: "Can't Remove", message: "\(dataObject) can't be deleted, add another to delete \(dataObject)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
func Duplicate() {
let alertController = UIAlertController(title: "Duplicate", message: "\(dataObject) is already in your pages!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
func max() {
let alertController = UIAlertController(title: "Maximum Reached", message: "\(dataObject) can't be added, you have reached the maximum of 5 coins. Please delete a coin to add another.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
and here is the DataViewController
import UIKit
class DataViewController: UIViewController {
#IBOutlet weak var dataLabel: UILabel!
//Variables and Objects.
//The dataObject carries the chosen cryptocurrencies ID from the CoinGecko API to use to get the correct data to load on each object.
var dataObject = String()
//The DefaultCurrency (gbp, eur...) chosen by the user.
var defaultCurrency = ""
//The Currency Unit taken from the exchange section of the API.
var currencyUnit = CGExchange.shared.exchangeData[0].rates.gbp.unit
var secondaryUnit = CGExchange.shared.exchangeData[0].rates.eur.unit
var tertiaryUnit = CGExchange.shared.exchangeData[0].rates.usd.unit
//Index of the dataObject
var dataIndex = Int()
//Objects
let cryptoLabel = UILabel()
let cryptoIconImage = UIImageView()
let secondaryPriceLabel = UILabel()
let mainPriceLabel = UILabel()
let tertiaryPriceLabel = UILabel()
//Custom Fonts.
let customFont = UIFont(name: "AvenirNext-Heavy", size: UIFont.labelFontSize)
let secondFont = UIFont(name: "AvenirNext-BoldItalic" , size: UIFont.labelFontSize)
//Setup Functions
//Get the index of the dataObject
func getIndex() {
let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
dataIndex = index
}
//Label
func setupLabels() {
//cryptoLabel from dataObject as name.
cryptoLabel.text = CGPrices.shared.coinData[dataIndex].name
//Prices from btc Exchange rate.
let btcPrice = CGPrices.shared.coinData[dataIndex].current_price!
let dcExchangeRate = CGExchange.shared.exchangeData[0].rates.gbp.value
let secondaryExchangeRate = CGExchange.shared.exchangeData[0].rates.eur.value
let tertiaryExchangeRate = CGExchange.shared.exchangeData[0].rates.usd.value
let realPrice = (btcPrice * dcExchangeRate)
let secondaryPrice = (btcPrice * secondaryExchangeRate)
let tertiaryPrice = (btcPrice * tertiaryExchangeRate)
secondaryPriceLabel.text = "\(secondaryUnit)\(String((round(1000 * secondaryPrice) / 1000)))"
mainPriceLabel.text = "\(currencyUnit)\(String((round(1000 * realPrice) /1000)))"
tertiaryPriceLabel.text = "\(tertiaryUnit)\(String((round(1000 * tertiaryPrice) / 1000)))"
}
//Image
func getIcon() {
let chosenImage = CGPrices.shared.coinData[dataIndex].image
let remoteImageUrl = URL(string: chosenImage)
guard let url = remoteImageUrl else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
DispatchQueue.main.async {
self.cryptoIconImage.image = UIImage(data: data)
}
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// for family in UIFont.familyNames.sorted() {
// let names = UIFont.fontNames(forFamilyName: family)
// print("Family: \(family) Font names: \(names)")
// }
// Do any additional setup after loading the view, typically from a nib.
self.setupLayout()
self.getIndex()
self.setupLabels()
self.getIcon()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.dataLabel!.text = dataObject
view.backgroundColor = .lightGray
}
}
Edit: CGPrices Class with getData method:
import Foundation
class CGPrices {
struct Coins: Decodable {
let id: String
let name: String
let symbol: String
let image: String
let current_price: Double?
let low_24h: Double?
//let price_change_24h: Double?
}
var coinData = [Coins]()
var defaultCurrency = ""
var coins = Manager.shared.coins
var coinsEncoded = ""
static let shared = CGPrices()
func encode() {
for i in 0..<coins.count {
coinsEncoded += coins[i]
if (i + 1) < coins.count { coinsEncoded += "%2C" }
}
print("encoded")
}
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
encode()
let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let coinsData = try JSONDecoder().decode([Coins].self, from: data)
self.coinData = coinsData
completion(arr)
} catch let jsonErr {
print("error serializing json: \(jsonErr)")
print(data)
}
}.resume()
}
func refresh(completion: () -> ()) {
defaultCurrency = UserDefaults.standard.string(forKey: "DefaultCurrency")!
completion()
}
}
I figured it out.
The problem was inside my getData method I was not updated the coins array:
var coinData = [Coins]()
var defaultCurrency = ""
var coins = Manager.shared.coins
var coinsEncoded = ""
static let shared = CGPrices()
func encode() {
for i in 0..<coins.count {
coinsEncoded += coins[i]
if (i+1)<coins.count { coinsEncoded+="%2C" }
}
print("encoded")
}
I needed to add this line in getData:
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
//Adding this line to update the array so that the URL is appended correctly.
coins = Manager.shared.coins
encode()
let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"
This would fix the finding nil in the DataViewController, but the app would still crash do to updating UI Elements on a background thread, as the segue was called inside the completion handler of the getData method. to fix this, I used DispatchQue.Main.Async on the segue inside the getData method in the addButton function, to ensure that everything is updated on the main thread, like so:
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
print("starting")
CGPrices.shared.getData(arr: true) { (arr) in
print("complete")
print(CGPrices.shared.coinData)
//Here making sure it is updated on main thread.
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
}
}
searchBar.text = ""
}
Thanks for all the comments as they helped me to figure this out, and I learned a lot in doing so. Hopefully this can help someone else in their thought process when debugging, as one can get so caught up in one area of a problem, and forget to take a step back and look to other areas.

swift - cannot make my if statement work is well as i need

i have a problem when i typed my code as is bellow but i got error ?!
i want user if typed code = 4234 on textFields and tap on "ADD" = "https://pastebin.com/raw/4234"
else = any url "http://www.example.com/"
#IBAction func btnPlusPressed(_ sender: UIButton)
{
let alert = UIAlertController(title: "Provider M3U URL", message: "Add Provided URL to add you M3U Plailist", preferredStyle: .alert)
let loginAction = UIAlertAction(title: "ADD", style: .default, handler: { (action) -> Void in
var url = alert.textFields![0]
if (url.text == "535" as String ){
let strURLl : String = "https://pastebin.com/raw/\(url.text!)"
}else{
let strURLl : String = "\(url.text!)"
}
UserDefaults .standard .set(strURLl, forKey: "URL")
let channelsVC = self.storyboard!.instantiateViewController(withIdentifier: "ChannelsViewController") as! ChannelsViewController
channelsVC.strURL = strURLl!
self.navigationController?.pushViewController(channelsVC, animated: true)
})
why error appear like this : (Use of unresolved identifier 'strURLl')
Here you are defining the constant strURLl within the scope of the IF statement.
It means you cannot use it once outside of those { } around it
if (url.text == "535" as String ){
let strURLl : String = "https://pastebin.com/raw/\(url.text!)"
} else {
let strURLl : String = "\(url.text!)"
}
Same problem with the else statement.
Solution
Simply declare the constant outside of the IF scope
let strURLl: String
if (url.text == "535" as String ){
strURLl = "https://pastebin.com/raw/\(url.text!)"
} else {
strURLl = "\(url.text!)"
}

How to select a contact with ABPeoplePickerNavigationController in Swift?

I have added the ABPeoplePickerNavigationController into my first view controller. I want that when I select a contact show the info to show in other view controller, but I'm trying use my code and this not show never when I click in a contact. This only open the contact into native app ABPeoplePickerNavigationController.
var people = ABPeoplePickerNavigationController()
var addressBook: ABAddressBookRef?
func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? {
if let ab = abRef {
self.view.addSubview(people.view)
return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue()
}
return nil
}
I tried this function
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!,didSelectPerson person: ABRecordRef!) {
var unmanagedEmails = ABRecordCopyValue(people, kABPersonEmailProperty)
let emailObj: ABMultiValueRef = Unmanaged.fromOpaque(unmanagedEmails.toOpaque()).takeUnretainedValue() as NSObject as ABMultiValueRef
var index = 0 as CFIndex
var unmanagedEmail = ABMultiValueCopyValueAtIndex(emailObj, index)
var emailAddress:String = Unmanaged.fromOpaque(unmanagedEmail.toOpaque()).takeUnretainedValue() as NSObject as String
println(emailAddress)
}
Thanks!
Here is the latest framework for iOS 9 - ContactsUI
import ContactsUI
Conform to the CNContactPickerDelegate (No required methods)
Create a contacts picker object and present it:
let peoplePicker = CNContactPickerViewController()
peoplePicker.delegate = self
self.presentViewController(peoplePicker, animated: true, completion: nil)
Dismiss the CNContactPickerViewController in the contactPickerDidCancel delegate function
func contactPickerDidCancel(picker: CNContactPickerViewController) {
picker.dismissViewControllerAnimated(true, completion: nil)
}
Here is how I Accessed a contacts name, phone numbers, phone number labels, and photo using the didSelectContact delegate function:
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
//Dismiss the picker VC
picker.dismissViewControllerAnimated(true, completion: nil)
//See if the contact has multiple phone numbers
if contact.phoneNumbers.count > 1 {
//If so we need the user to select which phone number we want them to use
let multiplePhoneNumbersAlert = UIAlertController(title: "Which one?", message: "This contact has multiple phone numbers, which one did you want use?", preferredStyle: UIAlertControllerStyle.Alert)
//Loop through all the phone numbers that we got back
for number in contact.phoneNumbers {
//Each object in the phone numbers array has a value property that is a CNPhoneNumber object, Make sure we can get that
if let actualNumber = number.value as? CNPhoneNumber {
//Get the label for the phone number
var phoneNumberLabel = number.label
//Strip off all the extra crap that comes through in that label
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("_", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("$", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("!", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("<", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString(">", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
//Create a title for the action for the UIAlertVC that we display to the user to pick phone numbers
let actionTitle = phoneNumberLabel + " - " + actualNumber.stringValue
//Create the alert action
let numberAction = UIAlertAction(title: actionTitle, style: UIAlertActionStyle.Default, handler: { (theAction) -> Void in
//Create an empty string for the contacts name
var nameToSave = ""
//See if we can get A frist name
if contact.givenName == "" {
//If Not check for a last name
if contact.familyName == "" {
//If no last name set name to Unknown Name
nameToSave = "Unknown Name"
}else{
nameToSave = contact.familyName
}
}else{
nameToSave = contact.givenName
}
// See if we can get image data
if let imageData = contact.imageData {
//If so create the image
let userImage = UIImage(data: imageData)
}
//Do what you need to do with your new contact information here!
//Get the string value of the phone number like this:
actualNumber.stringValue
})
//Add the action to the AlertController
multiplePhoneNumbersAlert.addAction(numberAction)
}
}
//Add a cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: { (theAction) -> Void in
//Cancel action completion
})
//Add the cancel action
multiplePhoneNumbersAlert.addAction(cancelAction)
//Present the ALert controller
self.presentViewController(multiplePhoneNumbersAlert, animated: true, completion: nil)
}else{
//Make sure we have at least one phone number
if contact.phoneNumbers.count > 0 {
//If so get the CNPhoneNumber object from the first item in the array of phone numbers
if let actualNumber = contact.phoneNumbers.first?.value as? CNPhoneNumber {
//Get the label of the phone number
var phoneNumberLabel = contact.phoneNumbers.first!.label
//Strip out the stuff you don't need
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("_", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("$", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("!", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString("<", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneNumberLabel = phoneNumberLabel.stringByReplacingOccurrencesOfString(">", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
//Create an empty string for the contacts name
var nameToSave = ""
//See if we can get A frist name
if contact.givenName == "" {
//If Not check for a last name
if contact.familyName == "" {
//If no last name set name to Unknown Name
nameToSave = "Unknown Name"
}else{
nameToSave = contact.familyName
}
}else{
nameToSave = contact.givenName
}
// See if we can get image data
if let imageData = contact.imageData {
//If so create the image
let userImage = UIImage(data: imageData)
}
//Do what you need to do with your new contact information here!
//Get the string value of the phone number like this:
actualNumber.stringValue
}
}else{
//If there are no phone numbers associated with the contact I call a custom funciton I wrote that lets me display an alert Controller to the user
self.displayAlert("Missing info", message: "You have no phone numbers associated with this contact")
}
}
}
A couple of thoughts:
Have you set the peoplePickerDelegate property of the people picker controller? If you don't do that, it won't know to try to call these methods in your class. Thus:
people.peoplePickerDelegate = self
presentViewController(people, animated: true, completion: nil)
Your example method is referencing people when you call ABRecordCopyValue. That's your picker controller. I assume you meant to reference person, the ABRecordRef! that was passed as a parameter.
You might also want to make sure you actually have an email address before trying to access it. You can use ABMultiValueGetCount.
I also think you can also eliminate that fromOpaque/toOpaque dance.
This yields:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) {
let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue()
if ABMultiValueGetCount(emails) > 0 {
let index = 0 as CFIndex
let emailAddress = ABMultiValueCopyValueAtIndex(emails, index).takeRetainedValue() as! String
print(emailAddress)
} else {
print("No email address")
}
}
If you need to support iOS 7, too, use:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
print("email = \(email)")
peoplePicker.dismissViewControllerAnimated(true, completion: nil)
return false
}
You might, though, rather than assuming the user only wanted the first email address, instead, let them click through and pick one of the possible multiple email addresses the contact had. So, first, you might want to eliminate some of the "noise", by telling the picker that you only want to see email addresses:
people.peoplePickerDelegate = self
people.displayedProperties = [NSNumber(int: kABPersonEmailProperty)]
presentViewController(people, animated: true, completion: nil)
And then, remove the prior method we've been discussing, and instead implement:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecordRef!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as String
println("email = \(email)")
}
And to support iOS 7,0, too, you'd add:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
print("email = \(email)")
peoplePicker.dismissViewControllerAnimated(true, completion: nil)
return false
}
By the way, iOS 8 offers a feature to control whether a contact is enabled or not. Since you're supporting iOS 7 and 8, you'd want to employ that conditionally, such as:
if people.respondsToSelector(Selector("predicateForEnablingPerson")) {
people.predicateForEnablingPerson = NSPredicate(format: "emailAddresses.#count > 0")
}
This gives the user visual indication whether there is even an email address for the individual, and prevents them from selecting entry without email address.
Obviously, if using iOS 9 and later, you should retire all of this and use the ContactsUI framework, which simplifies the code further.
SWIFT3 IOS10
Working version of Jon Vogel for Swift 3 and IOS 10 and support to multiple contacts selection.
//
// Created by JEFFERSON A NEITZKE on 30/01/17.
// Copyright © 2017 JEFFERSON A NEITZKE. All rights reserved.
//
import UIKit
import ContactsUI
class Principal: UIViewController, CNContactPickerDelegate {
var numeroADiscar: String = ""
var userImage: UIImage? = nil
var nameToSave = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let peoplePicker = CNContactPickerViewController()
peoplePicker.delegate = self
self.present(peoplePicker, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
picker.dismiss(animated: true, completion: nil)
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
// I only want single selection
if contacts.count != 1 {
return
} else {
//Dismiss the picker VC
picker.dismiss(animated: true, completion: nil)
let contact: CNContact = contacts[0]
//See if the contact has multiple phone numbers
if contact.phoneNumbers.count > 1 {
//If so we need the user to select which phone number we want them to use
let multiplePhoneNumbersAlert = UIAlertController(title: "Which one?", message: "This contact has multiple phone numbers, which one did you want use?", preferredStyle: UIAlertControllerStyle.alert)
//Loop through all the phone numbers that we got back
for number in contact.phoneNumbers {
//Each object in the phone numbers array has a value property that is a CNPhoneNumber object, Make sure we can get that
let actualNumber = number.value as CNPhoneNumber
//Get the label for the phone number
var phoneNumberLabel = number.label
//Strip off all the extra crap that comes through in that label
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "_", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "$", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "!", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "<", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: ">", with: "")
//Create a title for the action for the UIAlertVC that we display to the user to pick phone numbers
let actionTitle = phoneNumberLabel! + " - " + actualNumber.stringValue
//Create the alert action
let numberAction = UIAlertAction(title: actionTitle, style: UIAlertActionStyle.default, handler: { (theAction) -> Void in
//See if we can get A frist name
if contact.givenName == "" {
//If Not check for a last name
if contact.familyName == "" {
//If no last name set name to Unknown Name
self.nameToSave = "Unknown Name"
}else{
self.nameToSave = contact.familyName
}
} else {
self.nameToSave = contact.givenName
}
// See if we can get image data
if let imageData = contact.imageData {
//If so create the image
self.userImage = UIImage(data: imageData)!
}
//Do what you need to do with your new contact information here!
//Get the string value of the phone number like this:
self.numeroADiscar = actualNumber.stringValue
})
//Add the action to the AlertController
multiplePhoneNumbersAlert.addAction(numberAction)
}
//Add a cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { (theAction) -> Void in
//Cancel action completion
})
//Add the cancel action
multiplePhoneNumbersAlert.addAction(cancelAction)
//Present the ALert controller
self.present(multiplePhoneNumbersAlert, animated: true, completion: nil)
} else {
//Make sure we have at least one phone number
if contact.phoneNumbers.count > 0 {
//If so get the CNPhoneNumber object from the first item in the array of phone numbers
let actualNumber = (contact.phoneNumbers.first?.value)! as CNPhoneNumber
//Get the label of the phone number
var phoneNumberLabel = contact.phoneNumbers.first!.label
//Strip out the stuff you don't need
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "_", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "$", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "!", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: "<", with: "")
phoneNumberLabel = phoneNumberLabel?.replacingOccurrences(of: ">", with: "")
//Create an empty string for the contacts name
self.nameToSave = ""
//See if we can get A frist name
if contact.givenName == "" {
//If Not check for a last name
if contact.familyName == "" {
//If no last name set name to Unknown Name
self.nameToSave = "Unknown Name"
}else{
self.nameToSave = contact.familyName
}
} else {
nameToSave = contact.givenName
}
// See if we can get image data
if let imageData = contact.imageData {
//If so create the image
self.userImage = UIImage(data: imageData)
}
//Do what you need to do with your new contact information here!
//Get the string value of the phone number like this:
self.numeroADiscar = actualNumber.stringValue
} else {
//If there are no phone numbers associated with the contact I call a custom funciton I wrote that lets me display an alert Controller to the user
let alert = UIAlertController(title: "Missing info", message: "You have no phone numbers associated with this contact", preferredStyle: UIAlertControllerStyle.alert)
let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
}
}
}
}