I was trying to do something in Swift that would be easy in Objective-C using KVC. The new Contacts framework added in iOS9 is for the most part easier to use than the old AddressBook API. But finding a contact by its mobile phone number seems to be difficult. The predicates provided for finding contacts are limited to the name and the unique identifier. In Objective-C you could get all the contacts and then use an NSPredicate to filter on a KVC query. The structure is:
CNContact->phoneNumbers->(String, CNPhoneNumber->stringValue)
Presume in the code below that I fetched the contacts via:
let keys = [CNContactEmailAddressesKey,CNContactPhoneNumbersKey, CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)]
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
var contacts:[CNContact] = []
try! CNContactStore().enumerateContactsWithFetchRequest(fetchRequest) { ...
I want to compare the stringValue to a known value. Here's what I have so far from a playground:
import UIKit
import Contacts
let JennysPhone = "111-867-5309"
let SomeOtherPhone = "111-111-2222"
let AndAThirdPhone = "111-222-5309"
let contact1 = CNMutableContact()
contact1.givenName = "Jenny"
let phone1 = CNPhoneNumber(stringValue: JennysPhone)
let phoneLabeled1 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone1)
contact1.phoneNumbers.append(phoneLabeled1)
let contact2 = CNMutableContact()
contact2.givenName = "Billy"
let phone2 = CNPhoneNumber(stringValue: SomeOtherPhone)
let phoneLabeled2 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone2)
contact2.phoneNumbers.append(phoneLabeled2)
let contact3 = CNMutableContact()
contact3.givenName = "Jimmy"
let phone3 = CNPhoneNumber(stringValue: SomeOtherPhone)
let phoneLabeled3 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone3)
contact3.phoneNumbers.append(phoneLabeled3)
let contacts = [contact1, contact2, contact3]
let matches = contacts.filter { (contact) -> Bool in
let phoneMatches = contact.phoneNumbers.filter({ (labeledValue) -> Bool in
if let v = labeledValue.value as? CNPhoneNumber
{
return v.stringValue == JennysPhone
}
return false
})
return phoneMatches.count > 0
}
if let jennysNum = matches.first?.givenName
{
print("I think I found Jenny: \(jennysNum)")
}
else
{
print("I could not find Jenny")
}
This does work, but it's not efficient. On a device I would need to run this in a background thread, and it could take a while if the person has a lot of contacts. Is there a better way to find a contact by phone number (or email address, same idea) using the new iOS Contacts framework?
If you are looking for a more Swift-y way to do it:
let matches = contacts.filter {
return $0.phoneNumbers
.flatMap { $0.value as? CNPhoneNumber }
.contains { $0.stringValue == JennysPhone }
}
.flatMap casts each member of phoneNumbers from type CNLabeledValue to type CNPhoneNumber, ignoring those that cannot be casted.
.contains checks if any of these phone numbers matches Jenny's number.
I'm guessing you're wanting a more swift-y way, but obviously anything you can do in Obj-C can also be done in swift. So, you can still use NSPredicate:
let predicate = NSPredicate(format: "ANY phoneNumbers.value.digits CONTAINS %#", "1118675309")
let contactNSArray = contacts as NSArray
let contactsWithJennysPhoneNumber = contactNSArray.filteredArrayUsingPredicate(predicate)
Related
let's imagine that we have 2 entities:
-People (name, age, ..)
-House (color)
we recorded the data several times with house.addToPeople (newPeople) for each house
we want to get all the people of the house colored blue
how do we fetch this?
I tried this code but it gets all the people
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let peopleFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "People")
let houseFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "House")
houseFetch.fetchLimit = 1
houseFetch.predicate = NSPredicate(format: "color = %#", "blue")
...
let res = try? context.fetch(peopleFetch)
let resultData = res as! [People]
how to do this ?
Try this function. What it does is fetching all of the People and creating an array with all of the results.
func getAllItems() -> [People]? {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "People")
request.returnsObjectsAsFaults = false
do {
let result: NSArray = try context.fetch(request) as NSArray
return (result as? [People])!
} catch let error {
print("Errore recupero informazioni dal context \n \(error)")
}
return nil
}
If you want to perform your search following certain criteria such as a color, use the following code after request:
//Here i'm searching by index, if you need guidance for your case don't hesitate asking
request.predicate = NSPredicate(format: "index = %d", currentItem.index)
Edit: actually the code above is just to get all of the people, if you want to base your search on the houses do the following:
func retrieve()-> [People]{
//Fetch all of the houses with a certain name
let houseRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "House")
houseRequest.predicate = NSPredicate(format: "name = %#", "newName") //This seearch is by name
houseRequest.returnsObjectsAsFaults = false
do {
//put the fetched items in an array
let result: NSArray = try context.fetch(houseRequest) as NSArray
let houses = result as? [Houses]
//Get the people from the previous array
let people: [People]? = (houses.people!.allObjects as! [People])
return people
} catch let error {
print("Errore recupero informazioni dal context \n \((error))")
}
return nil
}
Thank you for your answer !
In this example "houses" is an array, so we have to add an index ➔ houses[0].people!.AllObjects
And thank you very much for your explanation.
In this example, print gewicht gives an output of optional({10)} i need the output (10) as
an Int assigned to a variable . So the output has to be let mijnGewicht = 10
How can i do that. Iam new to swift, so excuse me for the question.
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let managedObjectContext = appDelegate!.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dogs")
fetchRequest.predicate = NSPredicate(format: "name = %#", "Toni")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.relationshipKeyPathsForPrefetching = ["gewicht"]
do {
let fetchedResults = try managedObjectContext.fetch(fetchRequest)
for i in fetchedResults {
dogs.append(i as! NSManagedObject)
for i in dogs {
let gewicht = i.value(forKeyPath: "gewicht.kg")
print(gewicht)
}
Dealing with unspecified NSManagedObject and value(forKeypath: is outdated.
Take advantage of the generic abilities of Core Data. The benefit is no type cast and no Any.
First declare dogs as
var dogs = [Dogs]()
By the way it's highly recommended to name entities in singular form. Semantically you have an array of Dog instances.
Create the fetch request for the specific NSManagedObject subclass Dogs
As gewicht is a to-many relationship you have to use a loop to get all values
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let managedObjectContext = appDelegate!.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Dogs>(entityName: "Dogs")
fetchRequest.predicate = NSPredicate(format: "name = %#", "Toni")
fetchRequest.returnsObjectsAsFaults = false
do {
let result = try managedObjectContext.fetch(fetchRequest)
dogs.append(contentsOf: result)
for dog in dogs {
for gewicht in dog.gewicht {
let kg = gewicht.kg
print(kg)
}
}
} catch { print(error) }
If the relationship and/or the attribute is declared optional (which is still unclear) you have to unwrap the optional.
And consider that the integer value is Int16, Int32 or Int64 (unfortunately this information is missing, too). There is no Int type in Core Data.
And it's up to you how to distinguish the many values.
Try changing your code like this, it checks if the value you are assigning is not nil and then prints it
for i in dogs {
if let mijnGewicht = i.value(forKeyPath: "gewicht.kg"){
print(mijnGewicht)
}
}
You can try something like this
let gewicht = i.value(forKeyPath: "gewicht.kg")
if let gewichtInt = gewicht as? Int {
print(gewichtInt)
}
for i in dogs {
if let gewicht = i.value(forKeyPath: "gewicht.kg") {
print(gewicht) // this will give you the safe value as Int, if the value is nill it will not come in this if condition
}
let x = i.value(forKeyPath: "gewicht.kg") ?? 0
print (x) //this will give you wrapped safe value of gewicht.kg if exists else it will give you 0
}
here in above example i have shown you two ways to safe cast a value from optional, you can also use guard or guard let on the basis of your requirement
Below I try to make an array ChatListings, but it doesn't work.
let chatRef = FIRDatabase.database().reference().child("chatListings")
override func viewDidLoad() {
super.viewDidLoad()
let firstQuery = chatRef.queryOrdered(byChild: "userID").queryEqual(toValue: userID)
firstQuery.observe(FIRDataEventType.value, with: { snapshot in
for child in snapshot.children {
print("child is \(child)")
if let dict = snapshot.value as? Dictionary<String, AnyObject>{
print("dict is \(dict)")
let roomKey = dict["chatRoomKey"] as! String
let oUID = dict["otherUserID"] as! String
let oUserName = dict["otherUserName"] as! String
let oProfilePic = dict["otherUserProfilePic"] as! String
let userIDTemp = dict["userID"] as! String
chatListing = ChatListing(chatRoomKey: roomKey, UID: userIDTemp, name: oUserName, otherUserID: oUID, otherUserProfilePicURL: oProfilePic)
chatListings.append(chatListing)
}
}
print("chatListings = \(chatListings)")
})
}
This crashes saying that the compiler unexpectedly found nil while unwrapping an Optional value. I don't know why it won't work. I've tried every which way I can find to extract the data that the compiler reads moments before crashing or failing to fill an array of my 'chatlisting' objects.
Here's an example of the data that the compiler reads but cannot extract with maybe 4 different coding attempts:
"-KjdSF97Q2z3afXzkwQ9": {
chatRoomKey = "-KjdSF97Q2z3afXzkwQ9";
messages = {
"-KjdSOVTsg8jEy6SeEA2" = {
MediaType = PHOTO;
fileUrl = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/mget8KN2nHe4sOhbnWTixYvCOrr2%2F515963239.371526?alt=media&token=6cb12ec1-5bdb-43a1-ab49-90c90570b341";
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
};
"-KjdSPxpNT0pkQ1y5-_1" = {
MediaType = VIDEO;
fileUrl = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/mget8KN2nHe4sOhbnWTixYvCOrr2%2F515963229.282051?alt=media&token=04671c8e-d7f1-49f2-81d0-09836c034ae2";
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
};
"-KjdVaVTfbaC-3S-91-A" = {
MediaType = TEXT;
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
text = The;
};
};
otherUserID = aRandomUser3611;
otherUserName = Michael;
otherUserProfilePic = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/ProfilePictures%2Fmget8KN2nHe4sOhbnWTixYvCOrr2%2FmediumProfilePicture.jpg?alt=media&token=d88afa5d-0db7-4ce2-95c9-3038ff592e9f";
userID = mget8KN2nHe4sOhbnWTixYvCOrr2;
I'm trying to extract all the data but the messages part, which I plan on doing later in the app.
This data (excluding the "messages" part) gets written in the chatViewController's viewDidLoad like this:
let preMessageRef = chatRef.childByAutoId()
chatListingID = preMessageRef.key
let initialChatRoomData = ["chatRoomKey": chatListingID, "otherUserID": otherUID, "otherUserName": otherUserName, "otherUserProfilePic": otherUserProfilePicURLString, "userID": userID]
preMessageRef.setValue(initialChatRoomData)
Retrieving data from Firebase Database has been completely hit or miss for me, with copying the successful attempts of extracting data rarely working twice. Their documentation is minimal to the point of leaving out way too much as it provides little help for how to extract data in real world contexts. Why do people like Firebase? It has been a very frustrating experience working with Firebase and I definitely regret it. But it's probably too late to turn back and go with something better, i.e. a platform that provides clear instruction for how to get it to work.
I think you just have a silly typo. Try this:
let childData = child as! FIRDataSnapshot
print("child key: \(childData.key)")
if let dict = childData.value as? Dictionary<String, AnyObject> {
...
}
that is, use child instead of snapshot.
Update. Turns out using NSDictionary, rather than Dictionary, fixed the dict constant crashes. But, besides compiler bugs, still not clear why...
I'm new here and I just can't figure how to solve this:
func sendMessage(msgText : String) {
let defaults = NSUserDefaults.standardUserDefaults()
let myRootRef = Firebase(url:"https://boiling-heat-3478.firebaseio.com/")
self.messageCount = String(Int(self.messageCount)!+1)
let messagesRef = myRootRef.childByAppendingPath("messages")
let messageIDRef = messagesRef.childByAppendingPath(self.messageCount)
let messageInfo = ["Text": self.messageText.text!, "SenderID": defaults.objectForKey("userID") as! String]
messageIDRef.updateChildValues(messageInfo)
}
On view did load:
let myRootRef = Firebase(url:"https://boiling-heat-3478.firebaseio.com/")
let messagesRef = myRootRef.childByAppendingPath("messages")
let ref1 = Firebase(url: String(messagesRef))
// Attach a closure to read the data at our posts reference
ref1.queryOrderedByKey().queryStartingAtValue(self.messageCount).observeEventType(.ChildAdded, withBlock: { snapshot in
dispatch_async(dispatch_get_main_queue()){
let path = String(snapshot.ref)
let parts = path.characters.split("/")
if(Int(self.messageCount) < Int(String(parts.last!))){ self.messageCount = String(Int(self.messageCount)!+1) }
let messageText = snapshot.value.objectForKey("Text") as! String
let senderNameAge = snapshot.value.objectForKey("SenderID") as! String
let msg = Message(nameAge: senderNameAge, image: nil, text: messageText)
self.messages.append(msg)
self.messageTableView.reloadData()
}
}, withCancelBlock: { error in
print(error.description)
})
Well, this is working fine. I can chat with 1 person and see replies in real-time, but when I try to send like "aaaa" from person 1 to 2 and "bbbb" from person 2 to 1: message "aaaa" shows only on person 1 screen and "bbbb" on person 2.
Also, I can see on firebase that one of those messages replaced the other on the same destination! How can I avoid or solve this? Thank you
You're creating a problem by trying to use a count to index your messages.
The Firebase documentation explicitly warns against using arrays, since (as you're finding out) it is difficult to keep the message count in sync between multiple clients.
Instead of keeping the count synchronized between clients, you can simply call childByAutoId on a Firebase reference to generate a new child ID that is guaranteed to be unique and always incrementing.
let messagesRef = myRootRef.childByAppendingPath("messages")
let messageIDRef = messagesRef.childByAutoId()
let messageInfo = ["Text": self.messageText.text!, "SenderID": defaults.objectForKey("userID") as! String]
messageIDRef.updateChildValues(messageInfo)
See this section of the documentation for more information and examples.
Also see this example of a chat app written in Swift, that you can use for inspiration.
i'm trying to work my way through a plist...
Now i wanted to only fetch item 1 if it was a genre of Soft how do i achieve this ?
i'm trying to sort my way through it but it doesn't work...
var path = NSBundle.mainBundle().pathForResource("radioChannels", ofType: "plist")
self.menuItemArray = NSMutableArray(contentsOfFile: path!) as NSMutableArray!
for obj: AnyObject in menuItemArray {
if let dict = obj as? NSDictionary {
if let menuPunkt = dict["genre"] as? String {
if menuPunkt as String == ("Soft"){
println("Soft \(menuPunkt)")
}
} else {
println("failed with menuPunkt")
}
} else {
println("failed to convert to NSDictionary")
}
}
this i only tried but doest work
var descriptor: NSSortDescriptor = NSSortDescriptor(key: "Soft", ascending: true)
self.sortedResults = menuItemArray.sortedArrayUsingDescriptors([descriptor])
As mentioned in the comments, use the filter built-in. The following snippet will give you an array of items with a genre of "soft":
if let menuItemArray = menuItemArray as? [[String:AnyObject]] {
var softItems = menuItemArray.filter({
if let genre = $0["genre"] as? String {
return genre.lowercaseString == "soft"
}
return false
})
println("\(softItems)")
}
I prefer this solution over the NSPredicate solution for a couple of reasons, first, once you typecast the NSMutableArray into a swift array of the appropriate type, it's pretty much pure swift, and second, NSPredicate is a pretty heavy Objective-C bat for a really simple problem.
Also note that the typecast/check should really be done one-time when the plist is loaded rather than doing it each time you build your view contents, it's really only included in the snippet for completeness.
Also give this code a try!
if let path = NSBundle.mainBundle().pathForResource("radioChannels", ofType: "plist") {
if let myArray = NSArray(contentsOfFile: path) {
for dict in myArray {
if let genre = dict["genre"] as? String{
if genre == "genre1" {
print("genre1")
}
}
}
}
}
Why don't you use NSPredicate for filtering genre?
let predicate = NSPredicate(format: "genre = 'soft'")!
let filteredArray = self.menuItems.filteredArrayUsingPredicate(predicate)