How do I pull a contacts mailing address using the mailing address label?
The function buildContactsAddress_Array below builds an array containing the address label(name) and the address ID. The array is used to populate a tableView where the user can select the address by its name. I have included pretty much all of the related code to try and make things as clear as I can. Thanks in advance.
This is the part I want to change or replace to use address label. Right now it just uses the first/home address.
if let firstPostalAddress = (theName.postalAddresses.first),
let labelValuePair = firstPostalAddress.value(forKey: "labelValuePair") as? AnyObject,
let finalPostalAddress = labelValuePair.value(forKey: "value") as? CNPostalAddress
{
mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
}
struct contactAddresses
{
var theLabel: String
var theID: String
}
private var addressesArray = [contactAddresses]()
private var addressID: String = ""
private var theContactID: String = ""
This func pulls the contacts info using the contacts ID.
func getContactFromID_Ouote(contactID: String)
{
let store = CNContactStore()
var theName = CNContact()
let theKeys = [CNContactNamePrefixKey,
CNContactGivenNameKey,
CNContactFamilyNameKey,
CNContactOrganizationNameKey,
CNContactPostalAddressesKey,
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] as! [CNKeyDescriptor]
do {
theName = try store.unifiedContact(withIdentifier: contactID, keysToFetch: theKeys)
contactName = CNContactFormatter.string(from: theName, style: .fullName)!
contactPrefix = theName.namePrefix
contactFirst = theName.givenName
contactLast = theName.familyName
companyName = theName.organizationName == "" ? "" : theName.organizationName
} catch {
print("Fetching contact data failed: \(error)")
}
if let firstPostalAddress = (theName.postalAddresses.first),
let labelValuePair = firstPostalAddress.value(forKey: "labelValuePair") as? NSObject,
let finalPostalAddress = labelValuePair.value(forKey: "value") as? CNPostalAddress
{
mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
}
}
This func puts the contacts addresses into an array. The array is then used to populate a tableView.
func buildContactsAddress_Array(contactID: String)
{
let store = CNContactStore()
var theName = CNContact()
let theKeys = [CNContactPostalAddressesKey] as [CNKeyDescriptor]
do {
theName = try store.unifiedContact(withIdentifier: contactID, keysToFetch: theKeys)
let postalAddress = theName.postalAddresses
postalAddress.forEach { (mailAddress) in
// Strip forst 4 and last 4 from _$!<Home>!$_
let aaa = mailAddress.label
let bbb = aaa!.dropLast(4)
let ccc = bbb.dropFirst(4)
addressesArray.append(contactAddresses(theLabel: String(ccc), theID: mailAddress.identifier))
}
addressesArray.sort { $0.theLabel < $1.theLabel }
} catch {
print("Fetching contact addresses failed: \(error)")
}
}
This is the tableView extension. When a cell is tapped, addressID is populated with the ID of the appropriate mailing address.
extension QuotePreview_VC: UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return addressesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let theCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
theCell.textLabel?.text = addressesArray[indexPath.row].theLabel
return theCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
addressID = addressesArray[indexPath.row].theID
populateThePrintFld()
closeThePicker()
}
}
Here is what I ended up doing.
if addressesArray.count > 1
{
// Grab the address picked by the user in the TV
if let addressID = addressID
{
if let finalPostalAddress = theName.postalAddresses.first(where: { labelValuePair in labelValuePair.identifier == addressID })?.value
{
mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
}
}
} else {
// Grab the first available address
if let firstPostalAddress = (theName.postalAddresses.first),
let labelValuePair = firstPostalAddress.value(forKey: "labelValuePair") as? NSObject,
let finalPostalAddress = labelValuePair.value(forKey: "value") as? CNPostalAddress
{
mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
}
}
Related
I have a tableview that is being populated with who a user is following. Problem is that I need to pass that cells data to "var otherUser: NSDictionary!" but because I am populating the cell using a data structure file called "Information" I get this error - "Cannot assign value of type 'Information' to type 'NSDictionary?'" in the prepareForSegue. I am unsure if I can repackage the information I need into a NSDictionary so I can successfully do a data pass. I just don't know if this is a easy solution or an actual problem because of my ignorance.
Following TableViewController Code
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = self.yourFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
"Information" Data Structure File
import UIKit
class Information {
var uid: String
var businessName: String
var businessStreet: String
var businessCity: String
var businessState: String
init(uid: String, businessName: String, businessStreet: String, businessCity: String, businessState: String){
self.uid = uid
self.businessName = businessName
self.businessStreet = businessStreet
self.businessCity = businessCity
self.businessState = businessState
}
}
The error is pretty clear.
user in ExploreBusinessProfileSwitchView is obviously declared as NSDictionary, declare it as Information.
By the way don't use NSArray / NSDictionary in Swift. Use native types.
hi am fetching from data from firebase and stored into custom class and displaying into the tableview, when I displaying tableview its not showing empty but when I am calling with another array data its working fine
class datapass {
var fullName : String?
var status : String?
var dateAndTimeofVisit :String?
var profilephot :String?
var uid :String?
var inviterUID: String?
init(fullname:String?,status:String?,dateAndTimeofVisit:String?,profilephot:String,uid:String?,inviterUID:String?) {
self.fullName = fullname
self.status = status
self.dateAndTimeofVisit = dateAndTimeofVisit
self.profilephot = profilephot
self.uid = uid
self.inviterUID = inviterUID
}
}
inside viewdidload
var datapassval = [datapass]()
HandedDataRef = Database.database().reference()
.child(Constants.FIREBASE_CHILD_VISITORS)
.child(Constants.FIREBASE_CHILD_PRE_APPROVED_VISITORS).child("-LHO1TuRZTKCZV5Mli13")
HandedDataRef?.observeSingleEvent(of: .value, with: {(snapshot) in
print("snapshot values",snapshot)
if snapshot.exists() {
let visitorData = snapshot.value as? [String: AnyObject]
print("visitordata",visitorData as Any)
let dateAndTimeOfVisit = visitorData?[VisitorListFBKeys.dateAndTimeOfVisit.key] as? String
let fullName = visitorData?[VisitorListFBKeys.fullName.key] as? String
let inviterUID = visitorData?[VisitorListFBKeys.inviterUID.key] as? String
let mobileNumber = visitorData?[VisitorListFBKeys.mobileNumber.key] as? String
let profilePhoto = visitorData?[VisitorListFBKeys.profilePhoto.key] as? String
let status = visitorData?[VisitorListFBKeys.status.key] as? String
let uid = visitorData?[VisitorListFBKeys.uid.key] as? String
self.datapassval.append(datapass.init(fullname: fullName, status: status , dateAndTimeofVisit: dateAndTimeOfVisit, profilephot: profilePhoto!, uid: uid, inviterUID: inviterUID))
}
})
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datapassval.count //count getting 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HandedThingsToGuestTableViewCell
let datamy = datapassval[indexPath.row]
print("datamy",datamy)
cell.lbl_VisiterName.text = datamy.fullName
}
its not showing any thing how to display custom class data where I did mistake
You need to reload the table
self.datapassval.append(datapass.init(fullname: fullName, status: status , dateAndTimeofVisit: dateAndTimeOfVisit, profilephot: profilePhoto!, uid: uid, inviterUID: inviterUID))
self.tableView.reloadData()
I have my firebase database structured like this:
Snap (-KWLSAIh5WJvNJOkxBEr) {
beschrijving = "description";
image = "link to image";
title = "title";
}
Snap (-KWLSTak0H20X_2Qnanv) {
beschrijving = "description";
image = "link to image";
title = "title";
}
This is the code I am using to display this in a TableView:
import UIKit
import Firebase
class NieuwsTableViewController: UITableViewController {
var users = [UsersII]()
let cellId = "IdCell"
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
}
func fetchUser() {
Database.database().reference().child("Blog").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = UsersII(dictionary: dictionary)
self.users.append(user)
print(snapshot)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> lllTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let user = users.reversed()[indexPath.row]
cell.textLabel?.text = user.name
return cell as! lllTableViewCell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = users.reversed()[indexPath.row]
guard let beschrijving = message.beschrijving else {
return
}
guard let image = message.plaatje else {
return
}
guard let titel = message.name else {
return
}
UserDefaults.standard.set(beschrijving, forKey: "nieuwsBeschrijving")
UserDefaults.standard.set(image,forKey: "nieuwsPlaatje")
UserDefaults.standard.set(titel, forKey: "nieuwsTitel")
self.performSegue(withIdentifier: "gotonews", sender: nil)
}
}
And I don't know if you will need this to answer this question but I'll also post the "UsersII" (defined as users just above the viewDidLoad method) in case this is needed to answer the question.
import UIKit
class UsersII: NSObject {
var name: String?
var beschrijving: String?
var plaatje: String?
init(dictionary: [String: Any]) {
self.name = dictionary["title"] as? String ?? ""
self.beschrijving = dictionary["beschrijving"] as? String ?? ""
self.plaatje = dictionary["image"] as? String ?? ""
}
}
so what I want to achieve is that if you click on one of the cells, you get the parent id of the article, so in this case that would be the "-KWLSAIh5WJvNJOkxBEr or -KWLSTak0H20X_2Qnanv" I mentioned above in my firebase database structure.
Here is what i was saying you to do:
Your model class:
class UsersII: NSObject {
var parentId: String?
var name: String?
var beschrijving: String?
var plaatje: String?
init(dictionary: [String: Any],parentId:String) {
self.name = dictionary["title"] as? String ?? ""
self.beschrijving = dictionary["beschrijving"] as? String ?? ""
self.plaatje = dictionary["image"] as? String ?? ""
self.parentId = parentId
}
}
Fetch user method:
func fetchUser() {
Database.database().reference().child("Blog").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = UsersII(dictionary: dictionary,parentId:snapshot.key)
self.users.append(user)
print(snapshot)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
And finaly you didSelect:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = users.reversed()[indexPath.row]
guard let beschrijving = message.beschrijving else {
return
}
guard let image = message.plaatje else {
return
}
guard let titel = message.name else {
return
}
guard let parentId = message.name else
{
return
}
UserDefaults.standard.set(beschrijving, forKey: "nieuwsBeschrijving")
UserDefaults.standard.set(image,forKey: "nieuwsPlaatje")
UserDefaults.standard.set(titel, forKey: "nieuwsTitel")
UserDefaults.standard.set(parentId,forKey: "nieuwsParentId")
self.performSegue(withIdentifier: "gotonews", sender: nil)
}
}
I've got my table view all set up to fetch contacts using the contacts framework. Currently, I have the following table view:
I am fetching my contacts with the CNContactStore.I am able to retrieve all the data I want out of my contacts.
I created the following struct that contains an array of ExpandableNames where each ExpandableNames contains an isExpanded boolean and an array of FavoritableContact.
struct ExpandableNames{
var isExpanded: Bool
var contacts: [FavoritableContact]
}
struct FavoritableContact {
let contact: CNContact
var hasFavorited: Bool
}
With this, I declared and initialized the following array:
var favoritableContacts = [FavoritableContact]()
As well as:
var twoDimensionalArray = [ExpandableNames]()
Once I initialized my arrays, I created a function that would fetch my contacts.
private func fetchContacts(){
let store = CNContactStore()
store.requestAccess(for: (.contacts)) { (granted, err) in
if let err = err{
print("Failed to request access",err)
return
}
if granted {
print("Access granted")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let fetchRequest = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
var favoritableContacts = [FavoritableContact]()
fetchRequest.sortOrder = CNContactSortOrder.userDefault
do {
try store.enumerateContacts(with: fetchRequest, usingBlock: { ( contact, error) -> Void in
favoritableContacts.append(FavoritableContact(contact: contact, hasFavorited: false))
})
let names = ExpandableNames(isExpanded: true, contacts: favoritableContacts)
self.twoDimensionalArray = [names]
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}else{
print("Access denied")
}
}
}
With this in mind, Is there a way for me to fetch these contacts in groups. When I say groups, I mean fetching contacts in alphabetical order from the ContactsUI like this:
I've tried other ways but I keep hitting the same wall where I need to take into account the fact that the standard English language alphabet might not be the user's preferred device language, diacritics, symbols, numbers and more. Therefore, If I could just retrieve it the way it is from the contactsUI, it would be great.
Thank you guys!
NOTE: if you want more details let me know!
SOLUTION SWIFT 4
I was able to find the solution with the help of rmaddy using the UILocalizedIndexedCollation class.
The following code represents my ConctactsVC:
import UIKit
import Contacts
class ContactsVC: UITableViewController {
let cellID = "cellID"
var contacts = [Contact]()
var contactsWithSections = [[Contact]]()
let collation = UILocalizedIndexedCollation.current() // create a locale collation object, by which we can get section index titles of current locale. (locale = local contry/language)
var sectionTitles = [String]()
private func fetchContacts(){
let store = CNContactStore()
store.requestAccess(for: (.contacts)) { (granted, err) in
if let err = err{
print("Failed to request access",err)
return
}
if granted {
print("Access granted")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let fetchRequest = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
fetchRequest.sortOrder = CNContactSortOrder.userDefault
do {
try store.enumerateContacts(with: fetchRequest, usingBlock: { ( contact, error) -> Void in
guard let phoneNumber = contact.phoneNumbers.first?.value.stringValue else {return}
self.contacts.append(Contact(givenName: contact.givenName, familyName: contact.familyName, mobile: phoneNumber))
})
for index in self.contacts.indices{
print(self.contacts[index].givenName)
print(self.contacts[index].familyName)
print(self.contacts[index].mobile)
}
self.setUpCollation()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}else{
print("Access denied")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(dimissContactsVC))
navigationItem.title = "Contacts"
navigationController?.navigationBar.prefersLargeTitles = true
//Changing section index color
self.tableView.sectionIndexColor = UIColor.red
// need to register a custom cell
tableView.register(ContactsCell.self, forCellReuseIdentifier: cellID)
fetchContacts()
//Test
// let contact1 = Contact(name: "Anuska", mobile: "123434")
//
// let contact2 = Contact(name: "Anuj Sinha", mobile: "2321234")
//
// let contact3 = Contact(name: "Maria", mobile: "343434")
//
// let contact4 = Contact(name: "Jacob", mobile: "34454545")
//
// let contact5 = Contact(name: "Macculam", mobile: "455656")
//
// let contact6 = Contact(name: "Sophia", mobile: "4567890")
//
// self.contacts = [contact1, contact2, contact3, contact4, contact5, contact6]
}
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
#objc func setUpCollation(){
let (arrayContacts, arrayTitles) = collation.partitionObjects(array: self.contacts, collationStringSelector: #selector(getter: Contact.givenName))
self.contactsWithSections = arrayContacts as! [[Contact]]
self.sectionTitles = arrayTitles
print(contactsWithSections.count)
print(sectionTitles.count)
}
#objc func dimissContactsVC(){
dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactsWithSections[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! ContactsCell
let cell = ContactsCell(style: .subtitle, reuseIdentifier: cellID)
cell.link = self // custom delegation
let contact = contactsWithSections[indexPath.section][indexPath.row]
cell.selectionStyle = .default
cell.textLabel?.text = contact.givenName + " " + contact.familyName
cell.detailTextLabel?.text = contact.mobile
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
//Changing color for the Letters in the section titles
override func tableView(_ tableView: UITableView, willDisplayHeaderView view:UIView, forSection: Int) {
if let headerTitle = view as? UITableViewHeaderFooterView {
headerTitle.textLabel?.textColor = UIColor.red
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sectionTitles
}
}
extension UILocalizedIndexedCollation {
//func for partition array in sections
func partitionObjects(array:[AnyObject], collationStringSelector:Selector) -> ([AnyObject], [String]) {
var unsortedSections = [[AnyObject]]()
//1. Create a array to hold the data for each section
for _ in self.sectionTitles {
unsortedSections.append([]) //appending an empty array
}
//2. Put each objects into a section
for item in array {
let index:Int = self.section(for: item, collationStringSelector:collationStringSelector)
unsortedSections[index].append(item)
}
//3. sorting the array of each sections
var sectionTitles = [String]()
var sections = [AnyObject]()
for index in 0 ..< unsortedSections.count { if unsortedSections[index].count > 0 {
sectionTitles.append(self.sectionTitles[index])
sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector) as AnyObject)
}
}
return (sections, sectionTitles)
}
}
I also have a model file called Contact:
#objc class Contact : NSObject {
#objc var givenName: String!
#objc var familyName: String!
#objc var mobile: String!
init(givenName: String, familyName: String, mobile: String) {
self.givenName = givenName
self.familyName = familyName
self.mobile = mobile
}
}
PICTURE:
I managed to solve it like this.
//keys with fetching properties
NSArray *keys = #[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactEmailAddressesKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
//Order contacts by Surname.
request.sortOrder = CNContactSortOrderFamilyName;
--OR YOU CAN--
//Order contacts by Name.
request.sortOrder = CNContactSortOrderGivenName;
I'm trying to find where is the nil when unwrapping. Here is the piece of code I have. The lines where the fatal errors are found are at:
1st file:
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
2nd file:
self.allLists.append(ShoppingList.init(dictionary: currentList))
This is from the shoppingList.swift file and the function is called in a controller
import Foundation
import Firebase
class ShoppingList{
let name: String
var totalPrice: Float
var totalItems: Int
var id: String
var date: Date
var ownerId: String
init(_name: String, _totalPrice: Float = 0, _id: String = "") {
name = _name
totalPrice = _totalPrice
totalItems = 0
id = _id
date = Date()
ownerId = "1234"
}
//creates shopping list item from this dictionary
init(dictionary: NSDictionary) {
name = dictionary[kNAME] as! String
totalPrice = dictionary[kTOTALPRICE] as! Float
totalItems = dictionary[kTOTALITEMS] as! Int
id = dictionary[kSHOPPINGLISTID] as! String
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
ownerId = dictionary[kOWNERID] as! String
}
func dictionaryFromItem(item: ShoppingList) -> NSDictionary {
return NSDictionary(objects: [item.name, item.totalPrice, item.totalItems, item.id, dateFormatter().string(from: item.date), item.ownerId], forKeys: [kNAME as NSCopying, kTOTALPRICE as NSCopying, kTOTALITEMS as NSCopying, kSHOPPINGLISTID as NSCopying, kDATE as NSCopying, kOWNERID as NSCopying])
}
Here is the controller:
import UIKit
import KRProgressHUD
class AllListsViewController: UIViewController, UITableViewDataSource,UITableViewDelegate{
#IBOutlet weak var tableView: UITableView!
var allLists:[ShoppingList] = []
var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
loadLists()
}
//MARK: TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allLists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let shoppingList = allLists[indexPath.row]
cell.textLabel?.text = shoppingList.name
return cell
}
//MARK: IBActions
#IBAction func addBarButonItemPressed(_ sender: Any) {
let alertController = UIAlertController(title: "Create Shopping List", message: "Enter the shopping list name", preferredStyle: .alert)
alertController.addTextField{ (nameTextField) in
nameTextField.placeholder = "Name"
self.nameTextField = nameTextField
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel){ (action) in
}
let saveAction = UIAlertAction(title: "Save", style: .default){ (action) in
if self.nameTextField.text != ""{
self.createShoppingList()
}else{
KRProgressHUD.showWarning(message: "Name is empty!")
}
}
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
self.present(alertController,animated: true, completion:nil)
}
//MARK: LoadList
func loadLists(){
//.values has all the info of the child
firebase.child(kSHOPPINGLIST).child("1234").observe(.value, with: {
snapshot in
self.allLists.removeAll()
//if we actually received smthing from firebase
if snapshot.exists(){
let sorted = ((snapshot.value as! NSDictionary).allValues as NSArray).sortedArray(using: [NSSortDescriptor(key: kDATE,ascending: false)])
for list in sorted {
let currentList = list as! NSDictionary
self.allLists.append(ShoppingList.init(dictionary: currentList))
}
} else {
print("no snapshot")
}
self.tableView.reloadData()
})
}
//MARK: Helper functions
func createShoppingList(){
let shoppingList = ShoppingList(_name: nameTextField.text!)
shoppingList.saveItemInBackground(shoppingList: shoppingList){ (error) in
if error != nil{
KRProgressHUD.showError(message: "Error creating shopping list")
return
}
}
}
}
Also the data formatter is a small function in another file.
import Foundation
import UIKit
private let dateFormat = "yyyyMMDDHHmmss"
func dateFormatter() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return dateFormatter
}
So you have a forced downcast and a forced optional unwrap on this line:
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
Either your dictionary isn't returning a string, or the string coming out of the dictionary isn't able to be processed as a date. My guess is it's the first problem as dates are often stored as epoch.
Try this instead of the line above. Add a breakpoint at the top and step through:
print(dictionary[kDATE])
if let dictValue = dictionary[kDATE] as? String {
print(dictValue)
if let unwrappedDate = dateFormatter().date(from: dictValue) {
date = unwrappedDate
}
}
If it fails on the first if-let then the return value is not a string. If it fails on the second the problem lies with the date formatter being unable to read the format.
The first print might give you a clue as to what type to cast to, the second could help you fix the format.
Try to be careful when force unwrapping,
optionalVar!
or for downcasting.
unknownType as! Type
You should really only "use the force" when you're absolutely sure there's no way the value will be nil.