Issue Saving/Displaying CKAssets - ios

I'm pretty new to swift, so please try to bear with me.
I'm currently able to download CKRecords and insert them into an array, "birdFacts". Each record includes a few strings, an image (CKAsset), a date, and an int. When they initially download from iCloud, everything works fine.
Everything is saving as expected, except the image. When I reload the data, the asset doesn't show up.
Here is my code to load saved data:
if let savedFacts = loadFacts() {
birdFacts = savedFacts
print("successfully loaded saved bird facts")
}
func loadFacts() -> [CKRecord]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(BirdFact.ArchiveURL.path!) as? [CKRecord]
}
This is my code to save the array:
func saveFacts() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(birdFacts, toFile: BirdFact.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save bird facts")
}
}
This is within my custom class definition file:
import UIKit
import CloudKit
class BirdFact: NSObject, NSCoding {
//MARK: PROPERTIES
var birdName: String
var photo: CKAsset
var birdFact: String
var date: NSDate
var sortingDate: Int
//MARK: TYPES
struct PropertyKey {
static let namekey = "name"
static let photokey = "photo"
static let factkey = "fact"
static let datekey = "date"
static let sortingDatekey = "sortingDate"
}
//MARK: INITIALIZATION
init?(birdName: String, photo: CKAsset, birdFact: String, date: NSDate, sortingDate: Int){
//init stored props
self.birdName = birdName
self.photo = photo
self.birdFact = birdFact
self.date = date
self.sortingDate = sortingDate
super.init()
if birdName.isEmpty || birdFact.isEmpty {
return nil
}
}
//MARK: NSCODING
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(birdName, forKey: PropertyKey.namekey)
aCoder.encodeObject(photo, forKey: PropertyKey.photokey)
aCoder.encodeObject(birdFact, forKey: PropertyKey.factkey)
aCoder.encodeObject(date, forKey: PropertyKey.datekey)
aCoder.encodeObject(sortingDate, forKey: PropertyKey.sortingDatekey)
}
required convenience init?(coder aDecoder: NSCoder) {
let birdName = aDecoder.decodeObjectForKey(PropertyKey.namekey) as! String
let photo = aDecoder.decodeObjectForKey(PropertyKey.photokey) as! CKAsset
let birdFact = aDecoder.decodeObjectForKey(PropertyKey.factkey) as! String
let date = aDecoder.decodeObjectForKey(PropertyKey.datekey) as! NSDate
let sortingDate = aDecoder.decodeObjectForKey(PropertyKey.sortingDatekey) as! Int
self.init(birdName: birdName, photo: photo, birdFact: birdFact, date: date, sortingDate: sortingDate)
}
//MARK: ARCHIVING PATHS
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("BirdFacts")
}
I'm saving whenever I finish downloading and sorting the records. Any idea what is going wrong?
EDIT: Included CloudKit query. I should note that I never save anything to iCloud, only download existing records.
func allprevFacts(date: String, olddate: Int){
act.startAnimating()
let container = CKContainer.defaultContainer()
let publicDB = container.publicCloudDatabase
//let factPredicate = NSPredicate(format: "recordID = %#", CKRecordID(recordName: date))
let factPredicate = NSPredicate(format: "date <= %#", NSDate())
let query = CKQuery(recordType: "BirdFacts", predicate: factPredicate)
publicDB.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
if error != nil {
print(error)
}
else {
dispatch_async(dispatch_get_main_queue()){
//print(results)
var count = 0
var sortedresults = [Int]()
for result in results! {
var b = result.valueForKey("sortingDate") as! Int
sortedresults.append(b)
}
print(sortedresults)
while count < sortedresults.count {
if sortedresults[count] <= olddate {
sortedresults.removeAtIndex(count)
}
else {
count = count + 1
}
}
print(sortedresults)
while sortedresults.count > 0 {
var d: Int = 0
let a = sortedresults.maxElement()
print(a)
while d < sortedresults.count{
if sortedresults[d] == a {
sortedresults.removeAtIndex(d)
self.birdFacts.append(results![d])
self.tableFacts.reloadData()
self.tableFacts.hidden = false
}
d = d + 1
print(d)
}
}
self.saveFacts()
print("saving bird facts")
self.tableFacts.hidden = false
}
}
}
act.stopAnimating()

Related

CoreData fetch - memory leaks

When I try to catch data from CoreData I get a memory leak.
My function for fetching is:
func fetchTableBodyData<T: TableBody, C: NSManagedObject>(from year: Int?, coreDataObject: C.Type, returnType: T.Type) -> [T] {
guard let name = C.entity().name else { return [] }
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: name)
fetchRequest.returnsObjectsAsFaults = false
if let year = year {
fetchRequest.predicate = NSPredicate(format: "date >= %# AND date <= %#", Date().startOfYear(year: year) as CVarArg, Date().endOfYear(year: year) as CVarArg)
}
do {
let fetched = try context.fetch(fetchRequest) // Memory leak is here
guard let casted = fetched as? [C] else { return [] }
return T.parseFromCoreData(from: casted)
} catch let er {
print(er.localizedDescription)
return []
}
}
Context is define:
private var context: NSManagedObjectContext {
CoreDataStack.managedObjectContext
}
I have another memory leak in parsing from CoreDataObject to Struct:
static func getYearlyDataFrom(object: YearObject) -> YearlyData? {
guard let objects = object.tableObjects else { return nil } // Memory leak
var yearlyData: YearlyData = YearlyData()
yearlyData.year = Int(object.year)
var tableDatas: [TableData] = []
for tableObject in objects { // Memory leak
guard let tableObj = tableObject as? TableObject else { continue }
guard let id = tableObj.id, let nameOfList = tableObj.nameOfList, let range = tableObj.nameOfList, let lastUpdate = tableObj.lastUpdate else { continue } // Memory leak
tableDatas.append(TableData(id: id, nameOfList: nameOfList, range: range, lastUpdate: lastUpdate))
}
var playlists: [Playlist] = []
for playlistObject in object.playlistObjects ?? NSSet() { // Memory leak
guard let playlistObj = playlistObject as? PlaylistObject else { continue }
guard let playlistId = playlistObj.playlistId, let date = playlistObj.date, let name = playlistObj.name, let imageURL = playlistObj.imageURL else { continue } // Memory leak
var image: UIImage? = nil
if let imageData = playlistObj.image {
image = UIImage(data: imageData)
}
playlists.append(Playlist(playlistId: playlistId, name: name, position: Int(playlistObj.position), date: date, imageURL: imageURL, image: image))
}
yearlyData.playlists = playlists
yearlyData.tables = tableDatas
return yearlyData
}
Did anybody have the same problem?
The context object is defined as a Computed-Property it should be a State-Property instead and shouldn't be computed each time it's called.
private var context = CoreDataStack.managedObjectContext

updating values in a realm database swift

I am trying to update the values on a realm database. If a user selects a row containing values I want to be able to update the values of that row. Here is my code but instead of updating, it creates another value in the database
func updateTodoList(todoList: TodoListModel, name: String, description: String, createdDate: Date, remiderDate: Date, photo: Data, isCompleted: Bool) -> Void {
try! database.write {
if name != "" {
todoList.name = name
} else {
todoList.name = "No extra information"
}
todoList.desc = description
todoList.createdDate = createdDate
todoList.remiderDate = remiderDate
todoList.photo = photo
todoList.isCompleted = false
}
}
my did select row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let todoList = todoItems?[indexPath.row]
let storyBoard: UIStoryboard = UIStoryboard(name: "AddTodoListSB", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: Constants.ADD_TODO_SB) as! AddTodoListVC
newViewController.loadViewIfNeeded()
let min = Date()
let max = Date().addingTimeInterval(60 * 60 * 60 * 60)
guard let itemPhoto = UIImagePNGRepresentation(newViewController.imageView.image!) else {return}
newViewController.picker.minimumDate = min
newViewController.picker.maximumDate = max
// newViewController.showDateTimePicker(sender: <#T##AnyObject#>)
newViewController.picker.completionHandler = { date in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
self.title = formatter.string(from: date)
let reminder = formatter.string(from: date)
TodoListFunctions.instance.updateTodoList(todoList: todoList!, name: newViewController.titleTxtField.text!, description: newViewController.moreInfoTxtView.text!, createdDate: (todoList?.createdDate)!, remiderDate: formatter.date(from: reminder)!, photo: itemPhoto, isCompleted: false)
}
tableView.reloadData()
self.present(newViewController, animated: true, completion: nil)
}
// TodolistModel
class TodoListModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name: String = ""
#objc dynamic var desc: String = "No Description"
#objc dynamic var photo: Data? = nil
#objc dynamic var createdDate: Date?
#objc dynamic var remiderDate: Date?
#objc dynamic var isCompleted = false
override static func primaryKey() -> String? {
return "id"
}
let parentCategory = LinkingObjects(fromType: CategoryModel.self, property: "items")
}
further codes would be supplied on request
To update an object it must have a primary key and after you edit it use
// if it doesn't exist it'll be added
database.add(editedObjc, update: true)
//
// create object 1 , note: r = database
let lista = TaskList()
lista.pid = 1
lista.name = "Whole List"
// create object 2
let lista2 = TaskList()
lista2.pid = 2
lista2.name = "Whole List 2"
// add to database by write
r.add([lista,lista2])
let stored = r.objects(TaskList.self)
print("before edit" , stored)
// edit name of object 2
lista2.name = "qqwwqwqwqwqwqwqwq"
// update the object after changing it's name
r.add(lista2, update: true)
let stored2 = r.objects(TaskList.self)
print("after edit" , stored2)

My weather data is not showing up in my view controller

I can't figure out why my view controller is not showing the data, even though I can see it in the output window.
Output:
Muḩāfaz̧at Al Jīzah
Clear
88.0
my code:
override func viewDidLoad() {
super.viewDidLoad()
loadCurrentWeather = currentWeatherData()
loadCurrentWeather.downloadWeatherData {
//setting uo UI to download data
self.updateTodayUI()
}
}
func updateTodayUI() {
locationLabel.text = loadCurrentWeather.cityName
weatherTypeLabel.text = loadCurrentWeather.weatherType
currentTempLabel.text = "\(loadCurrentWeather.currentTemp)"
weatherTypeImage.image = UIImage(named: loadCurrentWeather.weatherType)
}
My view controller in Xcode:
My view controller on iphone:
currentweatherData the code where I'm downloading the data form.
import UIKit
import Alamofire
class currentWeatherData {
var cityNameone: String!
var dateone: String!
var weatherTypeone: String!
var currentTempone: Double!
var cityName: String {
if cityNameone == nil {
cityNameone = ""
}
return cityNameone
}
var date: String {
if dateone == nil {
dateone = ""
}
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
let currentDate = dateFormatter.string(from: Date())
self.dateone = "Today, \(currentDate)"
return dateone
}
var weatherType: String{
if weatherTypeone == nil{
weatherTypeone = ""
}
return weatherTypeone
}
var currentTemp: Double {
if currentTempone == nil {
currentTempone = 0.0
}
return currentTempone
}
func downloadWeatherData(completed: DownloadComplete){
// to tell alamofire where to download the data
let weatherURL = URL (string: currentWeatherURL)!
Alamofire.request(weatherURL).responseJSON{ response in
let result = response.result
if let dictionary = result.value as? Dictionary<String, AnyObject>{
if let name = dictionary["name"] as? String {
self.cityNameone = name.capitalized
print(self.cityNameone ?? "No city name")
}
if let weather = dictionary["weather"] as? [Dictionary<String, AnyObject>]{
if let main = weather[0]["main"] as? String {
self.weatherTypeone = main.capitalized
print(self.weatherTypeone ?? "No weather type")
}
}
if let main = dictionary["main"] as? Dictionary<String, AnyObject> {
if let currentTemperature = main["temp"] as? Double {
let kelvintoFarenheit = (currentTemperature * (9/5) - 459.67)
let totalKelvinToFarenheit = Double(round(10 * kelvintoFarenheit/10))
self.currentTempone = totalKelvinToFarenheit
print(self.currentTempone ?? .nan)
}
}
}
}
completed()
}
}
Is problem with my code or my view controller? Is it something wrong with my constraints?
I can't seem to figure it out.
You are calling completed too early - before the JSON response arrives. You have to call it inside the closure of the responseJSON call instead:
Alamofire.request(weatherURL).responseJSON { response in
let result = response.result
// ...
completed()
}
I cannot see all of your code to troubleshoot, but you may have a concurrency issue. Try putting the call to updateTodayUI inside of viewDidLoad(_:) inside of an async block like this:
DispatchQueue.main.async {
updateTodayUI()
}
You can find more information on dispatch queues and concurrency in the documentation.

Taking so much time when i insert 10k data in coredata ?

In my project concept i need a insert 10k data when user open the application. I integrate core data for storing data but its take 1 to 5 minutes.
Here is my code ?
func inserChatMessage(_ message: String, chatId: String, onCompletion completionHandler:((_ message: ChatMessage) -> Void)?) {
var objMessage: ChatMessage? = nil
if let obj = ChatMessage.createEntity() {
objMessage = obj
}
objMessage?.messageId = ""
objMessage?.message = message
objMessage?.chatId = chatId
objMessage?.senderId = AIUser.current.userId
objMessage?.createAt = Date()
objMessage?.updateAt = Date()
let cManager = CoreDataManager.sharedManager
cManager.saveContext()
if let completionHandler = completionHandler, let objMessage = objMessage {
completionHandler(objMessage)
}
}
Coredata is not a threadsafe. And as per your requirement you need to save large amount of data on app launch. So If you will save those data using main thread, your app will get hanged. So Instead on saving large amount of data on main thread you can save those data on background thread. Coredata is supporting multi threading concept by providing parent child context concept.
I have done same in one of my project and its working fine. Here i have attached code.
func savePersonalMessagesOnBackGroundThread(arrMessages:NSArray,responseData:#escaping () -> Void)
{
print(arrMessages)
let temporaryChatContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
temporaryChatContext.parent = self.managedObjectContext
temporaryChatContext.perform({() -> Void in
for i in 0..<arrMessages.count
{
let msgDic = arrMessages[i] as! NSDictionary
_ = self.saveMessageInLocalDB(dictMessage: msgDic, managedObjectContext: temporaryChatContext, onBackground: true)
if i == arrMessages.count - 1 {
do {
try temporaryChatContext.save()
runOnMainThreadWithoutDeadlock {
DLog(message: "Thred \(Thread.isMainThread)")
if(self.managedObjectContext.hasChanges)
{
self.saveContext()
responseData()
}
}
}
catch {
print(error)
}
}
}
})
}
func saveMessageInLocalDB(dictMessage:NSDictionary, managedObjectContext:NSManagedObjectContext,onBackground:Bool) -> Chat
{
var chatObj : Chat! = Chat()
var receiveId: Int32!
var flag:Bool = false
print(dictMessage)
// let predicate = NSPredicate(format:"uniqueId == %# and senderId = %d and receiverId = %d","\(dictMessage.value(forKey:keyuniqueId)!)",Int32(dictMessage.value(forKey:keysenderId) as! Int64),Int32(dictMessage.value(forKey:keyreceiverId) as! Int64))
let predicate = NSPredicate(format:"uniqueId == %#","\(dictMessage.value(forKey:keyuniqueId)!)")
let objContext = managedObjectContext
let fetchRequest = NSFetchRequest<Chat>(entityName: ENTITY_CHAT)
let disentity: NSEntityDescription = NSEntityDescription.entity(forEntityName: ENTITY_CHAT, in: objContext)!
fetchRequest.predicate = predicate
fetchRequest.entity = disentity
do{
let results = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Chat]
if(results.count > 0)
{
chatObj = results[0]
chatObj.messageId = Int32(dictMessage.value(forKey:keymessageId) as! Int64)
chatObj.dateOnly = dictMessage.value(forKey:keydateOnly) as! String?
}
else{
//receiveId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
//self.createNewChatObject(dictMessage: dictMessage, receiverId: receiveId, managedObjectContext: managedObjectContext)
chatObj = NSEntityDescription.insertNewObject(forEntityName:ENTITY_CHAT,into: managedObjectContext) as? Chat
if dictMessage[keymessageId] != nil {
chatObj.messageId = dictMessage.value(forKey:keymessageId) as! Int32
}
if(chatObj.message?.length != 0)
{
chatObj.message = dictMessage.value(forKey:keychatMessage) as? String
}
chatObj.messageType = Int32(dictMessage.value(forKey:keymessageType) as! Int64)
chatObj.senderId = Int32(dictMessage.value(forKey:keysenderId) as! Int64)
if(chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!))
{
let contactObj = self.getContactByContactId(contactId: Int32(dictMessage.value(forKey:keysenderId) as! Int64))
if(contactObj == nil)
{
_ = self.saveUnknownUserASContact(msgDict: dictMessage as! Dictionary<String, Any>)
}
}
chatObj.receiverId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
chatObj.uniqueId = dictMessage.value(forKey:keyuniqueId) as? String
chatObj.mediaName = dictMessage.value(forKey:keymediaName) as? String
print(NSDate())
if dictMessage[keycreatedDate] != nil {
let utcDate : NSDate = DateFormater.getUTCDateFromUTCString(givenDate: dictMessage.value(forKey:keycreatedDate) as! String)
chatObj.createdDate = utcDate
chatObj.updatedDate = utcDate
}
else
{
chatObj.createdDate = NSDate()
chatObj.updatedDate = NSDate()
}
if(chatObj.senderId == Int32((APP_DELEGATE.loggedInUser?.id)!))
{
chatObj.chatUser = chatObj.receiverId
}
else
{
chatObj.chatUser = chatObj.senderId
}
if dictMessage[keystatus] != nil {
chatObj.status = Bool((dictMessage.value(forKey:keystatus) as! Int64) as NSNumber)
}
switch Int(chatObj.messageType)
{
case MSG_TYPE.MSG_Text.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_TEXT_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_TEXT_SENDER.rawValue))
case MSG_TYPE.MSG_Image.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_IMAGE_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_IMAGE_SENDER.rawValue))
self.saveMedia(chatObj: chatObj)
default :
// chatObj.cellType = Int32(CELL_TYPE.CELL_LOAD_MORE.rawValue)
break
}
}
// deviceMake = 1;
if(!onBackground)
{
self.saveContext()
}
}
catch
{
}
return chatObj
}
Using the basic example below I can insert 10k records very quickly. The main thing that has changed here compared to your code is that I loop through and create the entities and then call save() at the very end. So you are performing one write call to the db instead of 10k. You are writing more information in that one call but it is still much quicker.
import UIKit
import CoreData
class ViewController: UIViewController {
lazy var sharedContext: NSManagedObjectContext? = {
return (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
}()
override func viewDidLoad() {
super.viewDidLoad()
if let messages = getMessages(), messages.count > 0 {
printMessages(messages: messages)
} else {
loadChatMessages()
printMessages(messages: getMessages())
}
}
private func printMessages(messages: [Message]?) {
guard let messages = messages else { return }
for message in messages {
print(message.message)
}
}
private func getMessages() -> [Message]? {
let request = NSFetchRequest<Message>(entityName: "Message")
let messages = try? self.sharedContext?.fetch(request)
return messages ?? nil
}
private func loadChatMessages() {
var counter = 1
while counter <= 10000 {
let message = Message(entity: Message.entity(), insertInto: self.sharedContext)
message.message = "This is message number \(counter)"
message.read = false
message.timestamp = Date()
counter = counter + 1
}
do {
try self.sharedContext?.save()
} catch {
print(error)
}
}
}
As mentioned in my comment above, you can improve this further by doing it in the background (see Twinkle's answer for an example of how to switch to a background thread), you can also provide a pre-filled (pre-seeded) core data database that already contains the 10k records with your app. so it doesn't need to load this on initial load.
To do this you would fill the db locally on your dev machine and then copy it to the project bundle. On initial load you can check to see if your db filename exists in the documents folder or not. If it doesn't copy it over from the bundle and then use that DB for core data.

Copying high score to a readable variable to display in a label

I need help with my high score system. So far I have the receivedHighScore variable I made (see below) being displayed on a label. This works and displays the value if I hard code it, but I can't get it to display the retrievedHighScore. Even if I try and make a global variable and use that when displaying it, I get <TestGame.HighScore: 0x170224aa0>.
If anyone could help me save the current high score counter into Score.HighScore or wherever it needs to go that would be awesome.
Currently I have two parts. HighScore.swift which includes:
import Foundation
class HighScore: NSObject {
var highScore: Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(highScore, forKey: "highScore")
}
init(coder aDecoder: NSCoder!) {
highScore = aDecoder.decodeIntegerForKey("highScore")
}
override init() {
}
}
class SaveHighScore:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveHighScore(#highScore: HighScore) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if NSKeyedArchiver.archiveRootObject(highScore, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveHighScore() -> NSObject {
var dataToRetrieve = HighScore()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? HighScore {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
and in my GameViewController I have this:
var Score = HighScore()
var receivedHighScore = HighScore()
override func viewDidLoad() {
super.viewDidLoad()
SaveHighScore().ArchiveHighScore(highScore: Score)
var retrievedHighScore = SaveHighScore().RetrieveHighScore() as HighScore
println(retrievedHighScore)
receivedHighScore = SaveHighScore().RetrieveHighScore() as HighScore
}
func update() {
labelCounter.text = String(counter++)
var highScoreYes = counter
Score.highScore = highScoreYes-1
}
When you get the highscore using:
var retrievedHighScore = SaveHighScore().RetrieveHighScore() as HighScore
retrivedHighScore is set to an instance of the HighScore object.
To get the highscore value you need to access the highScore property that retrivedHighScore has:
let currentHighScore: Int=retrivedHighScore.highScore

Resources