Parsing CloudKit Error (CKError) - ios

I'm using CloudKit and I'm checking if a specific zone was already created.
For this example, let's say that a zone isn't set, so CloudKit retrieves me a CKError.
This CKError has a property called partialErrorsByItemID which is of type [AnyHashable : Error]?
Here's the code:
fileprivate func checkIfZonesWereCreated() {
let privateDB = CKContainer.default().privateCloudDatabase
let op = CKFetchRecordZonesOperation(recordZoneIDs: [zoneID1, zoneID2])
op.fetchRecordZonesCompletionBlock = { (dict, err) in
if let err = err as? CKError, let _err = err.partialErrorsByItemID {
print(_err)
/* prints
[AnyHashable(<CKRecordZoneID: 0x60800003cba0; ownerName=__defaultOwner__, zoneName=TestZone>): <CKError 0x60400005a760: "Zone Not Found" (26/2036); server message = "Zone 'TestZone' does not exist"; uuid = ...-2DF4E13F81E2; container ID = "iCloud.com.someContainer">]
*/
// If I iterate through the dictionary
_err.forEach({ (k, v) in
print("key:", k) // prints: key: <CKRecordZoneID: 0x60800002d9e0; ownerName=__defaultOwner__, zoneName=TestZone>
print("value:", v) // prints: value: <CKError 0x60400005a760: "Zone Not Found" (26/2036); server message = "Zone 'TestZone' does not exist"; uuid = ...-2DF4E13F81E2; container ID = "iCloud.com.someContainer
})
return
}
print("dict:", dict)
}
privateDB.add(op)
}
How do I parse this error? I need to access the zoneName ?

The key in _err is a CKRecordZoneID. Once you have that, use the zoneName property to get the zone name.
I would write your code as follows:
fileprivate func checkIfZonesWereCreated() {
let privateDB = CKContainer.default().privateCloudDatabase
let op = CKFetchRecordZonesOperation(recordZoneIDs: [zoneID1, zoneID2])
op.fetchRecordZonesCompletionBlock = { (dict, err) in
if let err = err as? CKError {
switch err {
case CKError.partialFailure:
if let _err = err.partialErrorsByItemID {
for key in _err.keys {
if let zone = key as? CKRecordZoneID {
let name = zone.zoneName
print("Missing zone: \(name)")
}
}
return
}
default:
break
}
}
print("dict:", dict)
}
privateDB.add(op)
}

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 ""
}
}
}

How to get custom value back from Spotlight with CSCustomAttributeKey

I am trying to get some data back from Core Spotlight which I am storing using a custom attribute key. Tested this on macOS and iOS as well, the result is always the same.
My test class:
import CoreSpotlight
class SpotlightSearch {
let domainId = "com.company.some"
let originalDataKeyName: String
init() {
self.originalDataKeyName = domainId.replacingOccurrences(of: ".", with: "_") + "_originalData"
}
func addToIndex(title: String, content: String) {
guard let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName, searchable: false, searchableByDefault: false, unique: false, multiValued: false)
else { return }
let uniqueId = "MyUniqueId" + title
let originalContent = NSString(string: content)
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String)
attributeSet.title = title
attributeSet.setValue(originalContent, forCustomKey: originalDataKey)
let item = CSSearchableItem(uniqueIdentifier: uniqueId, domainIdentifier: domainId, attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error.localizedDescription)")
} else {
print("Item '\(title)' successfully indexed!")
}
}
}
var query: CSSearchQuery?
func search(title: String) {
var allItems = [CSSearchableItem]()
let queryString = "title == '\(title)'cd"
let attributes = [ "title", originalDataKeyName ]
let newQuery = CSSearchQuery(queryString: queryString, attributes: attributes)
newQuery.foundItemsHandler = { (items: [CSSearchableItem]) -> Void in
allItems.append(contentsOf: items)
}
newQuery.completionHandler = { [weak self] (error: Error?) -> Void in
guard let originalDataKeyName = self?.originalDataKeyName,
let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName)
else { return }
print("Search complete")
for item in allItems {
let attributeSet = item.attributeSet
let customData = attributeSet.value(forCustomKey: originalDataKey)
// Always nil
if customData == nil {
print("\(String(describing: originalDataKeyName)) not found in \(attributeSet.description)")
} else if let originalData = customData as? NSData {
let data = Data(referencing: originalData)
if let originalString = String(data: data, encoding: .utf8) {
print("Found '\(originalString)'")
}
}
}
}
query = newQuery
newQuery.start()
}
}
On app init:
let newSpotlightSearch = SpotlightSearch()
newSpotlightSearch.addToIndex(title: "Banana", content: "🍌")
Later:
spotlightSearch.search(title: "Banana")
It will find the title, but will not give me back the custom attribute value. If I put a breakpoint after "// Always nil" and use po attributeSet I will get
(lldb) po attributeSet
{
"_kMDItemBundleID" = "de.axelspringer.SearchMac";
"_kMDItemDomainIdentifier" = "com.company.some";
"_kMDItemExpirationDate" = "2018-08-26 00:00:00 +0000";
"_kMDItemExternalID" = MyUniqueIdBanana;
"com_company_some_originalData" = "\Ud83c\Udf4c";
kMDItemTitle = Banana;
}
So the value is there, but Spotlight will not return it to me. Already tried to use NSData instead of NSString for the custom attribute, but same result.
Also found this orphaned question in the Apple developer forums:
CSCustomAttributeKey valueForCustomKey not working
I believe it's iOS issue.
While it's not fixed, maybe Apple will allow you to use a private API to do your thing.
So, attributeSet has private Dictionaries attributes and customAttributes. You can try to get those values using Key Value Coding and ObjC:
NSDictionary *attributes = [attributeSet valueForKey:#"attributes"];
id customData = attributes[originalDataKeyName];
OR
NSDictionary *customAttributes = [attributeSet valueForKey:#"customAttributes"];
id customData = customAttributes[originalDataKeyName];
Key type in those dictionaries is either NSString* or CSCustomAttributeKey*, so you can try supplying both originalDataKeyName and originalDataKey.

How to know if a Firebase has fetched a value

I have three functions getNewOrder(),storeOrderDetails(_ details:[String:String]) and getUserInfo(_ userID:String).
Function getNewOrder() is called first. It fetches new orders ( .childAdded values) and sends the dictionary to storeOrderDetails(_ details:[String:String]).
storeOrderDetails(_ details:[String:String])then segregate all the values and callsgetUserInfo(_ userID:String)` by passing it userID which was present in its details.
getUserInfo(_ userID:String) then fetches users details and returns user's
information.
However, the problem is [ userInfo = getUserInfo(_ userID:String) in storeOrderDetails(_ details:[String:String]) ] userInfo is always empty. Apparently func getUserInfo(_ userID:String) goes into a completion block after it has returned empty value.
I want these three functions to execute in sequential way.
Any advice is highly appreciated.
Please follow the below Links to review my code.
https://imgur.com/hNjvyDk
https://imgur.com/J0LMXMg
func childAdded(){
let ref = Database.database().reference().child("Orders").child(todaysDate)
ref.observe(.childAdded) { (snapshot) in
var details = [String:String]()
if let orderID = snapshot.key as? String {
ref.child(orderID).observeSingleEvent(of: .value, with: { (snap) in
self.newOrderTextView.text = ""
self.customerNameLabel.text = ""
self.customerPhoneLabel.text = ""
self.orderNumberLabel.text = ""
let enumerator = snap.children
while let rest = enumerator.nextObject() as? DataSnapshot {
details[rest.key as? String ?? ""] = rest.value as? String ?? ""
}
self.storeUserDetails(details)
})
}
}
}
func storeUserDetails(_ details:[String:String]){
if details["CustomerID"] != nil {
userInfo = getUserDetails(details["CustomerID"]!)
print(userInfo)
}
if !userInfo.isEmpty{
let order = OrderDatabase()
order.customerEmail = userInfo["Email"]!
order.customerName = userInfo["Name"]!
order.orderAcceptStatus = details["OrderStatus"]!
order.customerOrderNumber = details["orderNumber"]!
order.orderID = details["orderID"]!
order.time = details["Time"]!
order.customerFirebaseID = details["CustomerID"]!
self.orderDatabase[details["orderNumber"]!] = order
self.orderTable.reloadData()
}
}
func getUserDetails(_ userID:String) -> [String:String]{
var details = [String:String]()
let userDetailsReference = Database.database().reference().child("Users")
userDetailsReference.child(userID).observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if let dictionary = snapshot.value as? NSDictionary {
self.customerNameLabel.text = dictionary.value(forKey: "Name") as? String
self.customerPhoneLabel.text = dictionary.value(forKey: "Email") as? String
details["Name"] = dictionary.value(forKey: "Name") as? String
details["Email"] = dictionary.value(forKey: "Email") as? String
}
})
return details
}
From what I can see here, I am betting that the issue you are facing has to do with the fact that the methods are asynchronous. So one thing is not completely finished and some other method gets fired too soon. There are a few ways to deal with this issue. One is completion handlers, and the other is adding observers. Below is an example of doing both for Firebase. Here I'm asking a getLocationPhotos method to get all the photos from Firebase. Notice the observers and completion handler
func getLocationPhotos(coordinate:CLLocationCoordinate2D){
dbHandler.getImageFileNames(coordinateIn: coordinate) { (filenames) in
if filenames.isEmpty {
log.debug(String.warningGet + "filenames is empty")
return
}//if filenames.isEmpty
self.imageFiles = filenames.filter { $0 != "none" }
if self.imageFiles.isEmpty {
log.error(String.errorGet + "imageFiles array is empty")
return
}//if imageFiles.isEmpty
for file in self.imageFiles {
let reference = self.storageHandler.imageReference.child(file)
let download = self.imageView.sd_setImage(with: reference)
if let i = self.imageView.image {
self.imageArray.append(i)
self.collectionView.reloadData()
}//let i
download?.observe(.progress, handler: { (snapshot) in
guard let p = snapshot.progress else {
return
}//let p
self.progressView.progress = Float(p.fractionCompleted)
if self.progressView.progress == Float(1) {
self.progressView.isHidden = true
}
})//progress
download?.observe(.success, handler: { (snapshot) in
self.progressView.progress = 1
self.progressView.isHidden = true
self.collectionView.setNeedsLayout()
})//success
download?.observe(.failure, handler: { (snapshot) in
log.error(String.errorGet + "Error occured getting data from snapshot")
})//failure
}//for file
}//dbHandler

Wrong read from Firebase

I am uploading a product to Firebase using this code :
let storageRef = Storage.storage().reference().child("ProductsImages").child(product.UniqueID()).child("MainImage.png")
if let mainChosenImage = self.selectedImageToUpload
{
if let uploadData = UIImageJPEGRepresentation(mainChosenImage, 0.2)
{
storageRef.putData(uploadData, metadata: nil)
{
(StorageMetaData, error) in
if error != nil
{
print(error)
return
}
self.mainImageURL = StorageMetaData?.downloadURL()?.absoluteString
if let urlString = self.mainImageURL
{
self.ref.child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
self.ref.child("Users").child(user.uid).child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
product.AddImageURLToProduct(URL: urlString)
}
}
}
}
product.RegisterProductForAllUsers(database: self.ref)
product.RegisterProductForAddingUser(database: self.ref)
self.performSegue(withIdentifier: "unwindToMyProductsViewController", sender: self)
Now I know that writing an image like this is async (1), but after item is added (Let's say we ignore picture for now), I have this in Firebase:
saved Firebase Product
But when I go back to my collectionView and load the information (It happens in the ViewDidLoad method), this is the information I read:
Product information read
This is my code for ViewDidLoad:
if let currentUserID = loggedOnUserID
{
// Retrieve the products and listen for changes
databaseHandle = ref?.child("Users").child(currentUserID).child("Products").observe(.childAdded, with:
{ (snapshot) in
// Code to execute when new product is added
let prodValue = snapshot.value as? NSDictionary
let prodName = prodValue?["Name"] as? String ?? ""
let prodPrice = prodValue?["Price"] as? Double ?? -1
let prodDesc = prodValue?["Description"] as? String ?? ""
let prodURLS = prodValue?["MainImage"] as? String ?? ""
let prodAmount = prodValue?["Amount"] as? Int ?? 0
let prodID = snapshot.key
let prodToAddToView = Product(name: prodName, price: prodPrice, currency: "NIS", description: prodDesc, location: "IL",
toSell: false, toBuy: false, owner: currentUserID, uniqueID: prodID, amount: prodAmount)
if (prodURLS != "")
{
prodToAddToView.AddImageURLToProduct(URL: prodURLS)
}
self.products.append(prodToAddToView)
DispatchQueue.main.async
{
self.MyProductsCollection.reloadData()
}
}
) // Closes observe function
Also - my code writing to Database :
public func RegisterProductForAllUsers(database dataBase: DatabaseReference)
{
dataBase.child("Products").child(self.UniqueID()).child("Name").setValue(self.Name())
dataBase.child("Products").child(self.UniqueID()).child("UniqueID").setValue(self.UniqueID())
dataBase.child("Products").child(self.UniqueID()).child("Price").setValue(self.Price())
dataBase.child("Products").child(self.UniqueID()).child("Description").setValue(self.Description())
dataBase.child("Products").child(self.UniqueID()).child("ToBuy?").setValue(self.m_ToBuy)
dataBase.child("Products").child(self.UniqueID()).child("ToSell?").setValue(self.m_ToSell)
dataBase.child("Products").child(self.UniqueID()).child("Owner").setValue(self.m_Owner)
dataBase.child("Products").child(self.UniqueID()).child("Amount").setValue(self.m_Amount)
dataBase.child("Products").child(self.UniqueID()).child("MainImage").setValue(self.m_PicturesURLs.first)
}
I am writing "Name" first, which is maybe the reason I only read name properly? Is there a way to make all these writings be atomic ?
with only 1 value for some reason. (2)
1) Any way to make it sync ?
2) How can I read the proper values ?

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.

Resources