The goal here is to retrieve all the documents of a Firestore Database, then putting the data of those retrieved documents into a Table View. I'm using Xcode and Firebase.
For example, if I had a Firestore database with this data and documents:
Document 1
Name: Bob
Email: bob#gmail.com
Phone: 408-111-1234
Document 2
Name: Joe
Email: joe#yahoo.com
Phone: 408-338-4321
I would like to have it all in a table view, maybe something like this:
Bob - bob#gmail.com - 408-111-1234
Joe - joe#yahoo.com - 408-338-4321
So far, I have this set up:
TableViewController:
class PeopleViewController: UITableViewController {
#IBOutlet var table: UITableView!
var peopleArray = [] as [Array<Any>]
private var document: [DocumentSnapshot] = []
override func viewDidLoad() {
super.viewDidLoad()
table.tableFooterView = UIView(frame: CGRect.zero)
self.table.delegate = self
self.table.dataSource = self
loadData()
}
And the loadData() func:
func loadData() {
FirebaseFirestore.root.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let email = data["email"] as? String ?? ""
let phone = data["phone"] as? String ?? ""
let newPerson = ["name": name, "email": email, "phone": phone]
self.peopleArray.append(newPerson)
print(self.peopleArray)
}
self.table.reloadData()
}
}
}
}
The first thing wrong here is that there is this error:
Cannot convert value of type '[String : String]' to expected argument type '[Any]'
When I try to append a newPerson in the PeopleArray. I have searched this up, but none of the answers matched this specific error. The second thing is that I have no clue how to even start adding the data from the PeopleArray into the Table View.
I would love some help for both the error and the Table View. Anything is appreciated!
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
After doing what Vadian recommended, I am stuck with a new error.
attempt to insert row 2 into section 0, but there are only 1 rows in section 0 after the update
This is the updated func loadData:
func loadData() {
FirebaseFirestore.root.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
self.table.beginUpdates()
let data = document.data()
let name = data["name"] as? String ?? ""
let zip = data["zip"] as? String ?? ""
let sport = data["sport"] as? String ?? ""
let newPerson = Person(name: name, zip: zip, sport: sport)
self.people.append(newPerson)
let newIndexPath = IndexPath(row: self.people.count, section: 0)
self.table.insertRows(at: [newIndexPath], with: .automatic)
self.table.endUpdates()
}
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
}
}
I've read about this on another question about how the rows are off by 1, but even when I let indexPath equal to people.count+1 or people.count+2, the error persists. What am I doing wrong?
First of all the syntax
var peopleArray = [] as [Array<Any>]
is bad practice. If you want to declare an empty array write
var peopleArray : [Array<Any>] = []
or
var peopleArray = [Array<Any>]()
Second of all your array is a nested array which is not intended. You mean
var peopleArray = Array<Any>()
which is more descriptive using the alternative syntax
var peopleArray = [Any]()
Third of all as your data is clearly a more specific type than [Any] declare the array
var peopleArray = [[String:String]]()
This fixes the error.
Fourth of all the recommended data source is a custom struct
struct Person {
let name, email, phone: String
}
In this case declare the array (we know that people is plural so the suffix array is redundant).
var people = [Person]()
And populate it (you might reload the table view on the main thread)
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let email = data["email"] as? String ?? ""
let phone = data["phone"] as? String ?? ""
let newPerson = Person(name: name, email: email, phone: phone)
self.people.append(newPerson)
}
DispatchQueue.main.async {
self.table.reloadData()
}
I researched a lot and fixed the edited part in the question I posted. A simple -1 in the insert rows fixed the problem.
let newIndexPath = IndexPath(row: self.people.count-1, section: 0)
Vadian also helped me with the original problem, which was using
var peopleArray = [[String:String]]()
instead of the other incorrect format. I was then able to find how to insert rows by just simply using the table.insertrows function.
(I'm just posting this for a simplified answer for anyone else experiencing problems).
I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {
I want to give the users of my app the possibility to get their contacts out of their address book.
I can present the ABPeoplePickerNavigationController and the user can select a person and then the ABPPNC dismiss again.
Thats the code I used to present it:
let peopleController = ABPeoplePickerNavigationController()
peopleController.delegate = self
peopleController.predicateForSelectionOfPerson = NSPredicate(value: true)
self.presentViewController(peopleController, animated: true, completion: nil)
And the peoplePickerNavigationController didSelectPerson function is called but I don't get how to get Information out of the person of type ABRecord. At https://developer.apple.com/library/ios/documentation/AddressBookUI/Reference/ABPeoplePickerNavigationController_Class/index.html#//apple_ref/doc/constant_group/Address_Book_Properties Apple says there are some Address Book Properties like: ABPersonNamePrefixProperty or ABPersonMiddleNameProperty but I don't get how to work with it.
So basically I want to do something like this:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
// What I want to do:
let name = person.lastName
let firstName = person.firstName
let image = person.image
}
After a long search I found an answer that worked for me:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
var firstName: String?
if ABRecordCopyValue(person, kABPersonFirstNameProperty) != nil{
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty).takeRetainedValue() as? String
}
var lastName: String?
if ABRecordCopyValue(person, kABPersonLastNameProperty){
lastName = ABRecordCopyValue(person, kABPersonLastNameProperty).takeRetainedValue() as? String
}
var pImage: UIImage?
if person != nil && ABPersonHasImageData(person){
pImage = UIImage(data: ABPersonCopyImageData(person).takeRetainedValue())
}
}
it would be easy if you use "ABAddressBookRef" and get all contacts . then you can show it to user as you want and will help in future too.
I have A weird problem, I am trying to get all contacts from IOS addressbook API, while I am trying to get all the values of properties (First name, Last name, Emails and phone numbers of each contact I am g etting "fatal error: unexpectedly found nil while unwrapping an Optional value"), just when i run the the command on my device, but when I am running the command on the xcode simulator everything works fine?
here is my sample code:
func getContactNames() {
let people = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() as NSArray
for person in people {
let firstName = ABRecordCopyValue(person,
kABPersonFirstNameProperty).takeRetainedValue() as! String
let lastName = ABRecordCopyValue(person,
kABPersonLastNameProperty).takeRetainedValue() as! String
let email: ABMultiValueRef = ABRecordCopyValue(person,kABPersonEmailProperty).takeRetainedValue()
println("First name = \(firstName)")
println("Last name = \(lastName)")
println("Email = \(email)")
}
}
You may find the problem is with your use of as! String. If any entries in your address book do not have a First Name or a Last Name, then you are attempting to retain a nil, hence the error.
My solution to this problem has been to use code like this:
public func stringPropertyValue(record: ABRecord, id:ABPropertyID) -> String! {
var result: String! = nil
if let v = ABRecordCopyValue(record, id) {
result = v.takeRetainedValue() as! String
}
return result
}
The you can set up variables like this:
let firstName = stringPropertyValue(person, id:kABPersonFirstNameProperty)
let lastName = stringPropertyValue(person, id:kABPersonLastNameProperty)
The resulting variables are implied optionals, but they won't cause the unwrapping errors. The above function can be easily reworked into a class structure along the lines of the new Contacts framework in iOS 9 / Xcode 7
How do I get the application name in Swift?
Googling gave me this:
[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"];
I converted it to Swift; error - method doesn't exist:
NSBundle.mainBundle().infoDictionary.objectForKey("CFBundleName")
This should work:
NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
infoDictionary is declared as a var infoDictionary: [NSObject : AnyObject]! so you have to unwrap it, access it as a Swift dictionary (rather than use objectForKey), and, as the result is an AnyObject, cast it.
Update Swift 3 (Xcode 8 beta 2)
Always better to use constants (and optionals) where possible, too:
Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String
I believe this solution is more elegant. What's more, using object(forInfoDictionaryKey:) is encouraged by Apple:
"Use of this method is preferred over other access methods because it returns the localized value of a key when one is available."
extension Bundle {
var displayName: String? {
return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
}
}
Accessing bundle display name:
if let displayName = Bundle.main.displayName {
print(displayName)
}
I have created a simple extension to get the app name that is shown under the icon on the Home screen.
By default, apps only have CFBundleName set. Some apps, however, set CFBundleDisplayName (The user-visible name of the bundle) to change the title under the app icon. Adding spaces is often the case, e.g. bundle name "ExampleApp" could have bundle display name set to "Example App".
extension Bundle {
// Name of the app - title under the icon.
var displayName: String? {
return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
object(forInfoDictionaryKey: "CFBundleName") as? String
}
}
Usage:
let appName = Bundle.main.displayName
Same answer in Swift 4.2
extension Bundle {
class var applicationName: String {
if let displayName: String = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String {
return displayName
} else if let name: String = Bundle.main.infoDictionary?["CFBundleName"] as? String {
return name
}
return "No Name Found"
}
}
you can use it like below
Bundle.applicationName
OR, An other way would be to avoid static or class method or property but to add to instance level.
extension Bundle {
var applicationName: String {
if let displayName: String = self.infoDictionary?["CFBundleDisplayName"] as? String {
return displayName
} else if let name: String = self.infoDictionary?["CFBundleName"] as? String {
return name
}
return "No Name Found"
}
}
and all it like following
Bundle.main.applicationName
Hope this helps :)
Swift 4
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
simple way:
let appName = NSBundle.mainBundle().infoDictionary?[kCFBundleNameKey as String] as? String
convenient way:
extension NSBundle {
class func mainInfoDictionary(key: CFString) -> String? {
return self.mainBundle().infoDictionary?[key as String] as? String
}
}
print(NSBundle.mainInfoDictionary(kCFBundleNameKey))
kCFBundleNameKey – Standard Info.plist key, see more in CFBundle
// Returns app's name
public static var appDisplayName: String? {
if let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
return bundleDisplayName
} else if let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
return bundleName
}
return nil
}
let appDisplayName = Bundle.main.infoDictionary?["CFBundleName"] as? String
It's optional, so put it in if let or guard statement.
All answers that just return CFBundleName will often not return the name the user expects, as if bundles have a CFBundleDisplayName, then this key is displayed by Finder, system frameworks, and most other apps.
Most answers just directly access the info dictionary but info dictionaries can be localized by string files and when accessing them directly, this localization is also ignored and thus again a wrong name may be returned, as Finder will display the localized name.
While CFBundleDisplayName is optional in Info.plist files, CFBundleName actually isn't, but if you forget to add it, nothing will break in your system, so you have a corrupt info dict, yet most users will probably never notice and in that case the code most answers may not return anything meaningful at all.
Here's my solution (Swift 3):
private
func stripFileExtension ( _ filename: String ) -> String {
var components = filename.components(separatedBy: ".")
guard components.count > 1 else { return filename }
components.removeLast()
return components.joined(separator: ".")
}
func nameOfApp ( ) -> String {
let bundle = Bundle.main
if let name = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName")
?? bundle.object(forInfoDictionaryKey: kCFBundleNameKey as String),
let stringName = name as? String
{ return stringName }
let bundleURL = bundle.bundleURL
let filename = bundleURL.lastPathComponent
return stripFileExtension(filename)
}
How is this solution better?
It will check CFBundleDisplayName first and only fall back to CFBundleName if not present.
The object() method always operates on the localized version of the info dictionary, so if a localization exists, it will automatically be used.
If neither CFBundleDisplayName nor CFBundleName exist in the dictionary, the code falls back to just using the bundle filename on disk without the extension (so "My Cool App.app" will be "My Cool App"), this is a fallback so that this function will never return nil.
This one works for me in Swift 4.2
guard let dictionary = Bundle.main.infoDictionary else { return "" }
if let version: String = dictionary["CFBundleDisplayName"] as? String {
return version
} else {
return ""
}
This is what worked for me in Xcode 11.0 and Swift 5
let bundleID = Bundle.main.bundleIdentifier
let bundleInfoDict: NSDictionary = Bundle.main.infoDictionary! as NSDictionary
let appName = bundleInfoDict["CFBundleName"] as! String
print(bundleID!)
print(appName)
This should be more like what you are looking for:
let infoDictionary: NSDictionary = NSBundle.mainBundle().infoDictionary as NSDictionary!
let appName: NSString = infoDictionary.objectForKey("CFBundleName") as NSString
NSLog("Name \(appName)")
There may still be a better way to do this but it at least returns the app name correctly in my very limited testing...
Try this:
extension Bundle {
var displayName: String {
let name = object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
return name ?? object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String
}
}
let bundleInfoDict: NSDictionary = NSBundle.mainBundle().infoDictionary!
let appName = bundleInfoDict["CFBundleName"] as String
This one works perfect for me
let appName = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleDisplayName") as! String
For swift 5, iOS 13*
As mentioned before, it‘s an optional, so put it in a guard statement. I do this using a struct:
struct Model {
struct ProgVariablen{
static var appBundleName:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary!["CFBundleName"] as! String
}//end get
}//end computed property
static var appBundleShortVersion:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary ["CFBundleShortVersionString"] as! String
}//end get
}//end computed property
static var appBundleBuild:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary["CFBundleVersion"] as! String
}//end get
}//end computed property
//initialsieren der Variablen
init(
appBundleName:String,
appBundleShortVersion:String,
appBundleBuild:String,
)
{
// do here nothing for 'let'
// do here nothing for 'computed properties'
// your other ‘var’ are here like:
// ProgVariablen.var1 = var1
}//end init
}//end struct ProgVariablen
}//end struct Model
Usage:
print("Model.ProgVariablen.appBundleName: '\(Model.ProgVariablen.appBundleName)'")
extension NSApplication {
static var name: String {
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ?? ProcessInfo.processInfo.processName
}
}
// Bundle+appName.swift
extension Bundle {
var appName: String {
object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
object(forInfoDictionaryKey: "CFBundleName") as? String ??
""
}
}
// Usage (non optional)
let appName = Bundle.main.appName
Try this one,
let bundleID = NSBundle.mainBundle().bundleIdentifier