How to convert NSManagedObject Array into String Array - Swift 3 - ios

I want to convert NSManagedObject Array into String Array for Search purposes. (I don't want to use predicates)
The search would be performed on an attribute "Notes" of an entity "Documents"
var documents: [NSManagedObject] = [] // data is saved in this after fetch request !
var filtered = [String]()
var dataa = [String]()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//the line below is not working
// dataa = [String(describing:documents)]
filtered = dataa.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
// let range = tmp.rangeOfString(searchText, options: NSString.CompareOptions.CaseInsensitiveSearch)
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = true;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
fetched data is stored into documents (NSManagedObject) through this function & it is called on viewDidLoad()
func fetchDocuments() {
let context = getContext()
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Documents")
//PLEASE TELL ME SOME CODE TO SAVE ALL FETCHED NOTES IN "dataa" SO THAT I CAN PERFORM FILTERING.
// It would be done like obtaining value from the nsmanagedobject via (abc.value(forKeyPath: "note") as? String)!
do {
documents = try context.fetch(fetchRequest)
self.tableView.reloadData()
} catch let error as NSError {
let errorDialog = UIAlertController(title: "Error!", message: "Failed to save! \(error): \(error.userInfo)", preferredStyle: .alert)
errorDialog.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(errorDialog, animated: true)
}
}

Related

How to fetch contacts and store in array on iOS?

I am working on a Tinder Swiping application for iOS Contacts. I have imported the contacts library and successfully have gotten the contacts to print in the console. However, I am trying to dynamically add those names to a card like Tinder. I have created a model class to hold the name, however, I am unable to append my data to that model.
struct ContactInfo {
var name: String
}
let contactInfo = [ContactInfo]()
func fetchContacts(){
let contactStore = CNContactStore()
var contacts = [CNContact]()
let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
let request = CNContactFetchRequest(keysToFetch: keys)
do {
try contactStore.enumerateContacts(with: request) { (contact, stop) in
contacts.append(contact)
var info = contact.givenName + " " + contact.familyName
print(info)
}
} catch {
print(error.localizedDescription)
}
}
var userModels : [ContactInfo] = {
var model : [ContactInfo] = []
for n in 1...10 {
model.append(ContactInfo(name: names[Int(arc4random_uniform(UInt32(names.count)))]))
}
return model
}()
I would like all of my contacts to append to the model variable which is then returned to the cards.
As my understanding, the enumerateContacts is asynchronous. It means that, when your app is executing the line to create the userModels array, the contacts aren't fetched yet : so your array stays empty.
I would try to move the userModels array creation in another controller, where you are displaying your cards.
To achieve that, you can use a delegate, that receives the fetched contacts as a parameter. Then, you can assign your array with this parameter content and create your cards' content.
Here is a great tutorial on how to use a delegate with Swift.
Hope this will help you.
I have created helper class for that. That might help you.
Example
ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in
if error {
print("error")
}else {
print(contacts)
}
}
ContactSyncHelper
import UIKit
import Foundation
import Contacts
let kphone_number = "phone_number"
let kcountry_code = "country_code"
class ContactSyncHelper: NSObject {
static let sharedInstance: ContactSyncHelper = {
let instance = ContactSyncHelper()
// setup code
return instance
}()
// MARK: - Initialization Method
override init() {
super.init()
}
//Example
// ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in
// if error {
// print("error")
// }else {
// print(contacts)
// }
// }
func getAllContacts(completion: ([NSMutableDictionary],Bool) -> ()) {
switch CNContactStore.authorizationStatus(for: .contacts) {
// Update our UI if the user has granted access to their Contacts
case .authorized:
break
// Prompt the user for access to Contacts if there is no definitive answer
case .notDetermined :
completion([],true)
break
// Display a message if the user has denied or restricted access to Contacts
case .denied,
.restricted:
//CommData.showAlert(self, withMsg: "Permission was not granted for Contacts.", withTitle: "Privacy Warning!", action: nil)
completion([],true)
break
}
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey,
CNContactImageDataAvailableKey,
CNContactThumbnailImageDataKey] as [Any]
// Get all the containers
var allContainers: [CNContainer] = []
do {
allContainers = try contactStore.containers(matching: nil)
} catch {
print("Error fetching containers")
}
var arrayNumbers: [NSMutableDictionary] = []
for container in allContainers {
let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)
do {
let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
containerResults.forEach { (contact:CNContact) in
contact.phoneNumbers.forEach { (justPhone:CNLabeledValue) in
let numberValue = justPhone.value
let countryCode = numberValue.value(forKey: "countryCode") as? String
var strphone = numberValue.stringValue
strphone = strphone.replacingOccurrences(of: "(", with: "")
strphone = strphone.replacingOccurrences(of: ")", with: "")
strphone = strphone.replacingOccurrences(of: "-", with: "")
strphone = strphone.replacingOccurrences(of: "+", with: "")
strphone = strphone.components(separatedBy: .whitespaces).joined()
if strphone.hasPrefix("0"){
strphone.remove(at: (strphone.startIndex))
}
if(countryCode != nil)
{
var countryCode1:String = self.getCountryPhonceCode(country: countryCode!)
if strphone.hasPrefix(countryCode1) {
strphone = strphone.deletingPrefix(countryCode1)
}
countryCode1 = "+\(countryCode1)"
let dict = NSMutableDictionary()
dict.setValue(strphone, forKey: kphone_number)
dict.setValue(countryCode1, forKey: kcountry_code)
arrayNumbers.append(dict)
}
}
}
} catch {
print("Error fetching results for container")
}
}
completion(arrayNumbers,false)
}
func getCountryPhonceCode (country : String) -> String
{
if country.count == 2
{
let x : [String] = ["972","IL","93","AF","355","AL","213","DZ","1","AS","376","AD","244","AO","1","AI","1","AG","54","AR","374","AM","297","AW","61","AU","43","AT","994","AZ","1","BS","973","BH","880","BD","1","BB","375","BY","32","BE","501","BZ","229","BJ","1","BM","975","BT","387","BA","267","BW","55","BR","246","IO","359","BG","226","BF","257","BI","855","KH","237","CM","1","CA","238","CV","345","KY","236","CF","235","TD","56","CL","86","CN","61","CX","57","CO","269","KM","242","CG","682","CK","506","CR","385","HR","53","CU" ,"537","CY","420","CZ","45","DK" ,"253","DJ","1","DM","1","DO","593","EC","20","EG" ,"503","SV","240","GQ","291","ER","372","EE","251","ET","298","FO","679","FJ","358","FI","33","FR","594","GF","689","PF","241","GA","220","GM","995","GE","49","DE","233","GH","350","GI","30","GR","299","GL","1","GD","590","GP","1","GU","502","GT","224","GN","245","GW","595","GY","509","HT","504","HN","36","HU","354","IS","91","IN","62","ID","964","IQ","353","IE","972","IL","39","IT","1","JM","81","JP","962","JO","77","KZ","254","KE","686","KI","965","KW","996","KG","371","LV","961","LB","266","LS","231","LR","423","LI","370","LT","352","LU","261","MG","265","MW","60","MY","960","MV","223","ML","356","MT","692","MH","596","MQ","222","MR","230","MU","262","YT","52","MX","377","MC","976","MN","382","ME","1","MS","212","MA","95","MM","264","NA","674","NR","977","NP","31","NL","599","AN","687","NC","64","NZ","505","NI","227","NE","234","NG","683","NU","672","NF","1","MP","47","NO","968","OM","92","PK","680","PW","507","PA","675","PG","595","PY","51","PE","63","PH","48","PL","351","PT","1","PR","974","QA","40","RO","250","RW","685","WS","378","SM","966","SA","221","SN","381","RS","248","SC","232","SL","65","SG","421","SK","386","SI","677","SB","27","ZA","500","GS","34","ES","94","LK","249","SD","597","SR","268","SZ","46","SE","41","CH","992","TJ","66","TH","228","TG","690","TK","676","TO","1","TT","216","TN","90","TR","993","TM","1","TC","688","TV","256","UG","380","UA","971","AE","44","GB","1","US","598","UY","998","UZ","678","VU","681","WF","967","YE","260","ZM","263","ZW","591","BO","673","BN","61","CC","243","CD","225","CI","500","FK","44","GG","379","VA","852","HK","98","IR","44","IM","44","JE","850","KP","82","KR","856","LA","218","LY","853","MO","389","MK","691","FM","373","MD","258","MZ","970","PS","872","PN","262","RE","7","RU","590","BL","290","SH","1","KN","1","LC","590","MF","508","PM","1","VC","239","ST","252","SO","47","SJ","963","SY","886","TW","255","TZ","670","TL","58","VE","84","VN","284","VG","340","VI","678","VU","681","WF","685","WS","967","YE","262","YT","27","ZA","260","ZM","263","ZW"]
var keys = [String]()
var values = [String]()
let whitespace = NSCharacterSet.decimalDigits
//let range = phrase.rangeOfCharacterFromSet(whitespace)
for i in x {
// range will be nil if no whitespace is found
// if (i.rangeOfCharacterFromSet(whitespace) != nil) {
if (i.rangeOfCharacter(from: whitespace, options: String.CompareOptions.caseInsensitive) != nil) {
values.append(i)
}
else {
keys.append(i)
}
}
let countryCodeListDict:NSDictionary = NSDictionary(objects: values as [String], forKeys: keys as [String] as [NSCopying])
if let _: AnyObject = countryCodeListDict.value(forKey: country.uppercased()) as AnyObject {
return countryCodeListDict[country.uppercased()] as! String
} else
{
return ""
}
}
else
{
return ""
}
}
}

Filter searchText to tableView

Right now I am using following
let data = ["New York, NY", "Los Angeles, CA" . . .]
var filteredData: [String]!
filteredData = data
But I want to use Firebase, with an almost identical structure, by using this
var data = [Categories]()
(This is categories)
struct Categories {
let key:String!
let content:String!
let itemRef:FIRDatabaseReference?
init (content:String, key:String = "") {
self.key = key
self.content = content
self.itemRef = nil
}
init (snapshot:FIRDataSnapshot) {
key = snapshot.key
itemRef = snapshot.ref
if let CategoriesContent = snapshot.value!["content"] as? String {
content = CategoriesContent
} else {
content = ""
}
}
}
So that when I search for something these lines is supposed to filter out everything that aren't correct
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
// Unhide tableview, this will be changed to another method
tableView.hidden = false
filteredData = searchText.isEmpty ? data : data.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.rangeOfString(searchText) != nil
})
tableView.reloadData()
}
But since filter({(dataString: String) only takes strings it does not work
Question : Is there any other way to replace the string with my Firebase struct?
Thanks a lot!
this tutorial is so clear in UISearchResultsUpdating and Filtering section.

Displaying Search Results in UITableView

I have a UITableViewController and I've added a search bar as the header view. I'm trying to get the table view data to reload when the text in the search bar changes but it's not working.
Here is my textDidChange method. For context, jsonArray is [[String: AnyObject]] and is passed from another view controller. The searchResults array is then set to jsonArray in viewDidLoad() and the table view uses searchResults as it's data source.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
searchResults?.removeAll()
searchResults = jsonArray!
} else {
searchResults?.removeAll()
for market in jsonArray! {
let titleRange = market["name"]?.range(of: searchText, options: .caseInsensitive)
var address = String()
var city = String()
var state = String()
var zip = Int()
if let address1 = market["address1"] as? String {
address = address1
}
if let city1 = market["city"] as? String {
city = city1
}
if let state1 = market["state"] as? String {
state = state1
}
if let zip1 = market["zip"] as? Int {
zip = zip1
}
let zipCode = String(describing: zip)
let addressString = String("\(address) " + "\(city), " + "\(state) " + "\(zipCode)")
let addressRange = addressString?.range(of: searchText, options: .caseInsensitive)
if titleRange != nil {
searchResults?.append(market)
} else if addressRange != nil {
searchResults?.append(market)
}
}
}
tableView.reloadData()
}
I've added this print statement at the end of the else statement after the for loop and it's showing that the array contains all of the objects it contained originally so it looks like my ranges aren't working correctly.
print("search array: \(searchResults?.count)")
I fixed this by using NSRange instead of Range in Swift.
let titleNSRange: NSRange = (market["name"]?.range(of: searchText, options: .caseInsensitive))!
let addressNSRange: NSRange = (addressNSString.range(of: searchText, options: .caseInsensitive))
if titleNSRange.location != NSNotFound || addressNSRange.location != NSNotFound {
searchResults?.append(market)
}
you Don't need to call searchResults?.removeAll() function instead you use it
searchResults = NSMutableArray.init()
Don't use searchResults?.append(market) instead use it
searchResults.addObject(market)

leaky listener firebase ios

I am trying to load message box data for chat functionality.
The message box is loaded as:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser()!["firebaseUID"] !== nil)
{
print(PFUser.currentUser()!["firebaseUID"])
self.updateResultArray(PFUser.currentUser()!["firebaseUID"] as! String)
}
}
func updateResultArray(uid: String) {
let userName = String(PFUser.currentUser()!["username"])
//print("updateResultArray is getting called")
let userhandle = self.firebase.childByAppendingPath("users").childByAppendingPath(uid).childByAppendingPath("rooms").queryOrderedByValue()
.observeSingleEventOfType(.Value, withBlock: { roomsnapshot in
let enumerator = roomsnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
self.roomArray.append(rest.key)
}
//get the latest message from all the rooms
if self.roomArray.isEmpty == false
{
for i in 0...self.roomArray.count-1
{
print("in the room loop \(self.roomArray[i])")
let messagehandle = self.messagesRef.childByAppendingPath(self.roomArray[i]).queryOrderedByKey().queryLimitedToFirst(1).observeSingleEventOfType(.Value, withBlock: {
messagesnapshot in
print("the messagesnapshot child count is \(messagesnapshot.childrenCount)")
let enumerator = messagesnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
let sender = rest.value.objectForKey("sender") as? String
let reciever = rest.value.objectForKey("reciever") as? String
//print("sender is \(sender!) and reciever is \(reciever!)")
let eventhandle = self.firebase.childByAppendingPath("rooms").childByAppendingPath(self.roomArray[i]).observeSingleEventOfType(.Value, withBlock: { eventsnapshot in
if eventsnapshot.value is NSNull {
// The value is null
}
else
{
let eventAttr = eventsnapshot.value.objectForKey("eventAttributes") as? String
let eventDetails = eventsnapshot.value.objectForKey("eventDetails") as? String
//print("userName is \(userName)")
//print("sender is \(sender)")
if (userName != sender!) //for event joinee
{
let firstname1 = eventsnapshot.value.objectForKey("firstname1") as? String
self.otherNames.append(sender!)
self.resultsNameArray.append(firstname1!)
self.base4String = eventsnapshot.value.objectForKey("img1") as! String
self.resultsImageFiles.append(self.base4String)
}
else //for event creator
{
let firstname2 = eventsnapshot.value.objectForKey("firstname2") as? String
self.otherNames.append(reciever!)
self.resultsNameArray.append(firstname2!)
self.base4String = eventsnapshot.value.objectForKey("img2") as! String
self.resultsImageFiles.append(self.base4String)
}
let newlineChars = NSCharacterSet.newlineCharacterSet()
let evntArray = eventDetails!.componentsSeparatedByCharactersInSet(newlineChars).filter{!$0.isEmpty}
self.eventArray.append(evntArray[0])
self.eventdetailsArray.append(eventAttr!)
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.resultsTable.reloadData()
}
}
})
// self.firebase.removeAuthEventObserverWithHandle(eventhandle)
}
})
//self.messagesRef.removeAuthEventObserverWithHandle(messagehandle)
}
}
})
//firebase.removeAuthEventObserverWithHandle(userhandle)
}
since i am using observeSingleEventOfType i havent coded to remove handlers( i have tried that as well).
In the individual chat, the code is like this:
func refreshResults() {
print("the roomid is \(roomid)")
//update from firebase
let messagehandle = self.messagesRef.childByAppendingPath(roomid).queryOrderedByKey()
.observeEventType(.Value, withBlock: { messageTextsnapshot in
self.messageArray.removeAll()
self.senderArray.removeAll()
// print("the messageTextsnapshot child count is \(messageTextsnapshot.childrenCount)") // I got the expected number of items
let enumerator = messageTextsnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
let text = rest.value.objectForKey("message") as? String
let sender = rest.value.objectForKey("sender") as? String
if text != nil && text != ""
{
self.messageArray.append(text!)
self.senderArray.append(sender!)
}
}
for subView in self.resultsScrollView.subviews {
subView.removeFromSuperview()
}
for var i = 0; i <= self.messageArray.count-1; i++ {
if self.senderArray[i] == userName {
if (self.messageArray[i].rangeOfString(self.acceptMessage) != nil)
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.myImg, date: NSDate(), type: .AcceptMine)
self.addChatBubble(chatBubbleData)
}
else
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.myImg, date: NSDate(), type: .Mine)
self.addChatBubble(chatBubbleData)
}
} else {
if (self.messageArray[i].rangeOfString(self.acceptMessage) != nil)
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.otherImg, date: NSDate(), type: .Accept)
self.addChatBubble(chatBubbleData)
}
else
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.otherImg, date: NSDate(), type: .Opponent)
self.addChatBubble(chatBubbleData)
}
}
let bottomOffset:CGPoint = CGPointMake(0, self.resultsScrollView.contentSize.height - self.resultsScrollView.bounds.size.height)
self.resultsScrollView.setContentOffset(bottomOffset, animated: false)
}
})
self.messagesRef.removeAuthEventObserverWithHandle(messagehandle)
}
There are a few other listeners similar to this. the problem is when i go back from this view(individual chat to message box, the memory consumption increases. I have cleared all arrays and closed the handlers immediately after use. but still memory consumption increases and sometimes in message box same rows are replicated again. how should i solve this. I tried using
observeSingleEventOfType but it is not a correct solution as the data sync stops.
Used this as reference:
https://www.firebase.com/blog/2015-10-15-best-practices-uiviewcontroller-ios-firebase.html
It looks like your message box object is not being released due to a retain cycle caused by the listener callback block holding a reference to the message box object. You can alleviate this by using [weak self] in blocks that you pass to other objects. For example:
.observeSingleEventOfType(.Value, withBlock:
{
[weak self] roomsnapshot in
let enumerator = roomsnapshot.children
...
This makes 'self' an optional type, and you can then add:
guard let strongSelf = self else { ... }
The problem was that i was closing the listeners on the parent and not on the child. so the listeners were still in memory.
When i closed the listeners on the full path it worked.

CoreData add object

I made an app which's using core data. I made a function which saves 1 or 2 values / write data into core data. This is the following method:
func saveName(name: String) {
let myDate:NSDate = NSDate()
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as NSManagedObject
if markCell == true {
newManagedObject.setValue(name, forKey: "markedCell")
markCell = false
}
else {
newManagedObject.setValue(name, forKey: "name")
newManagedObject.setValue(myDate, forKey: "datum")
}
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
It occurs a crash in the function cellForRowAtIndexPath if markCell == true. If markCell == false (step into else) all works perfect.
If I run this function:
func saveName(name: String) {
let myDate:NSDate = NSDate()
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as NSManagedObject
newManagedObject.setValue(name, forKey: "markedCell")
markCell = false
newManagedObject.setValue(name, forKey: "name")
newManagedObject.setValue(myDate, forKey: "datum")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
no crash occurs but than I also added a value to markedCell. I only want to add a value into markedCell if the bool is set to true (the user pressed a button -> bool will be set to true and func saveNamewill be called).
Load data from core data (create UITableViewCell):
//Get task
let context = self.fetchedResultsController.managedObjectContext
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
var taskString:NSString
taskString = object.valueForKey("name") as String
cell.textLabel!.text = object.valueForKey("name") as? String
//Set accessory type
var request:NSFetchRequest = NSFetchRequest(entityName: "Person")
request.predicate = NSPredicate(format:"markedCell = %#", taskString)
var results : [NSManagedObject] = context.executeFetchRequest(request, error: nil) as [NSManagedObject]
if (results.count > 0) {
//Element exists
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
println("Cell is marked")
}
else {
//Doesn't exist
cell.accessoryType = UITableViewCellAccessoryType.None
println("Cell isn't marked")
}
I can bet that the problem comes from the fact that markedCell is declared as optional property in your Core Data model while name or/and datum are not optional.
If this is the case your saving works fine when you enter the else loop because at that point you have:
markedCell == nil //this is allowed in your Core Data model
name != nil
datum != nil
However, when you do not enter into the else loop you have:
markedCell != nil
name == nil
datum == nil
and one of the last two lines is incompatible with your Core Data model. If you want to use your original code you need to ensure that all properties mentioned here are declared as optional.

Resources