The app I made is post/thread based. Every time a client submits a post, all the other clients receive the post as they refresh the tableview. The newly received post is then saved using core data. Ultimately, for every client that refreshes, the function fetchPosts is called. fetchPost is an asynchronous function that returns a callback twice. First, when it receives posts from core data and then when server sync is finished and the live data has been received.
The problem with this function is that it will always return in the first callback all the posts, including the one that was deleted (by other clients).
What is a proper way to deal with that? Here is my code:
static func fetchPosts(lastPost:Topic?,subject:String,complition: #escaping (_ topics:[Topic?],_ newData:Bool)->()){
var topics:[Topic?] = []
//Check Ceche. FIRST PART
do {
let fetchRequest : NSFetchRequest<DPost> = DPost.fetchRequest()
fetchRequest.fetchLimit = 20
if lastPost == nil {
fetchRequest.predicate = NSPredicate(format: "created < %# AND subject = %# ", NSDate(),subject)
}else{
fetchRequest.predicate = NSPredicate(format: "created < %# AND subject = %#", argumentArray: [lastPost?.date as Any, subject])
}
let fetchedResults = try context.fetch(fetchRequest)
// _ = index
for (_, aPost) in fetchedResults.enumerated() {
topics.append(Topic(id: aPost.id!, title: aPost.title!, date: aPost.created! as Date, image: aPost.imageAddress, posterUsername: aPost.username!, posterUserid: aPost.userId!,posterImage: aPost.posterImageAddress))
//TODO: add subject id
}
}
catch {
print ("fetch task failed", error)
}
//First Callback
complition(topics,true)
//Second part
//Check server.
topics = []
var data:[String:Any] = [:]
data[K.UserInformation.sessionID] = User.currentUser!.sessionID
data[K.UserInformation.udid] = User.currentUser?.udid
if topics.last == nil {
data[K.TopicInformation.data] = "000000000000000000000000"
} else {
data[K.TopicInformation.data] = lastPost?.id
}
data[K.TopicInformation.subject] = subject
HTTPRequest.appSession.data_request(url_to_request: K.Server.domain+":8443/getPosts",method: HTTPRequest.HTTPRequestMethod.post, data: HTTPRequest.toJSON(dict: data)) { (resStr:String?) in
// return respond with information about the registrant status.
if resStr != nil{
let respond = HTTPRequest.toDict(jsonStr: resStr!)
if (respond[K.Status.success] != nil){
let postDictList = respond[K.Status.success] as! [[String:Any]]
if postDictList.count == 0 {
//Second callback
complition(topics,true)
return
}
for dict in postDictList {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
var topic:Topic? = nil
if let date = formatter.date(from: dict[K.TopicInformation.date] as! String) {
context.mergePolicy = NSOverwriteMergePolicy
let cPost = NSEntityDescription.insertNewObject(forEntityName: "CDPost", into: context) as! DPost
cPost.id = dict[K.TopicInformation.id] as? String
cPost.title = dict[K.TopicInformation.title] as? String
cPost.created = date as NSDate
cPost.imageAddress = dict[K.TopicInformation.postImageAddress] as? String
cPost.username = dict[K.TopicInformation.posterUsername] as? String
cPost.userId = dict[K.TopicInformation.posterUserid] as? String
cPost.posterImageAddress = dict[K.TopicInformation.posterImageAddress] as? String
cPost.subject = dict[K.TopicInformation.subject] as? String
do{
try context.save()
}
catch{
fatalError("Failure to save context: \(error)")
}
topic = Topic(id: dict[K.TopicInformation.id] as! String,
title: dict[K.TopicInformation.title] as! String,
date: date,
image: dict[K.TopicInformation.postImageAddress] as? String,
posterUsername: dict[K.TopicInformation.posterUsername] as! String,
posterUserid: dict[K.TopicInformation.posterUserid] as! String, posterImage: dict[K.TopicInformation.posterImageAddress] as? String)
}
topics.append(topic!)
}
complition(topics,true)
return
}
if(respond[K.Status.error] != nil){
print(respond["errmsg"] as! String)
}
}
The server side is written with NodeJS Mongodb is the database I'm using. Let me know if it's relevant so can edit out/in some tags.
If you have fetch limit, I dont think you can do it locally by compare the fetched posts and the stored posts in your CoreData, best is add an unread tag to the post and update it with your API, when fetch then can fetch both deleted and normal posts with unread tag, another idea is use last logged in time and fetch all post from that time, including deleted post
I'm having a modal entity file as below,
import UIKit
class MyProfile: NSObject {
var userName : String = ""
func initWithDict(dict: NSMutableDictionary) {
self.userName = dict.objectForKey("username") as! String
}
}
Saving that entity by encoding as below,
let myDict: NSMutableDictionary = ["username": "abc"]
let myEntity:MyProfile = MyProfile()
myEntity.initWithDict(myDict)
let userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(myEntity)
userDefaults.setObject(encodedData, forKey: "MyProfileEntity")
userDefaults.synchronize()
Getting that saved entity as below,
let myEntity:MyProfile = MyProfile()
let userDefaults = NSUserDefaults.standardUserDefaults()
guard let decodedNSData = userDefaults.objectForKey("MyProfileEntity") as? NSData,
myEntity = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? MyProfile!
else {
print("Failed")
return
}
print(myEntity.userName)
It's not working, having crashes and lot of syntax errors, I'm new to swift,
It's showing some syntax errors like definition conflicts with previous value in the unarchiveObjectWithData line. If I fix that error, then at the time of getting the entity from userdefaults it's crashing.
can anyone suggest how can I resolve it?
To save custom object into user default, you must implement NSCoding protocol. Please replace your custom data model like this:
class MyProfile: NSObject,NSCoding {
var userName : String = ""
#objc required init(coder aDecoder:NSCoder){
self.userName = aDecoder.decodeObjectForKey("USER_NAME") as! String
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.userName, forKey: "USER_NAME")
}
init(dict: [String: String]) {
self.userName = dict["username"]!
}
override init() {
super.init()
}
}
Here is the code for saving and retrieving MyProfile object:
// Save profile
func saveProfile(profile: MyProfile){
let filename = NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")
let data = NSKeyedArchiver.archivedDataWithRootObject(profile)
data.writeToFile(filename, atomically: true)
}
// Get profile
func getProfile() -> MyProfile?{
if let data = NSData(contentsOfFile: NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")){
let unarchiveProfile = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MyProfile
return unarchiveProfile
} else{
return nil
}
}
Now here is the code snippet how to use those method:
// Create profile object
let profile = MyProfile(dict: ["username" : "MOHAMMAD"])
// save profile
saveProfile(profile)
// retrieve profile
if let myProfile = getProfile(){
print(myProfile.userName)
}else{
print("Profile not found")
}
You can't do this:
let myEntity:MyProfile = MyProfile()
Then later on, do this:
myEntity = ...
When something is defined with 'let', you cannot change it.
Change to
var myEntity: MyProfile?
It is possible that
NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData)
is returning nil. You then proceed to force unwrapping by adding
as? MyProfile!
try changing this to
as? MyProfile
Then later, see if you got something back
if let myEntity = myEntity {
print(myEntity.userName)
}
Why when I print myData array in contains at first position all my valueForKey in nil, like this
func fetchFavorites(){
let fetchRequest = NSFetchRequest(entityName: "Favoritos")
do {
let result = try moc.executeFetchRequest(fetchRequest)
for managedObject in result {
if let Fid = managedObject.valueForKey("place_favorite_id"),
desc_place = managedObject.valueForKey("desc_place"),
place_name = managedObject.valueForKey("place_name"),
lat = managedObject.valueForKey("latitude"),
lon = managedObject.valueForKey("longitude") {
print("\(Fid) \(desc_place) \(place_name) \(lat) \(lon)")
Place_Id.append(Fid as! String)
}
}
myData = result
} catch {
let fetchError = error as NSError
print(fetchError)
}
}
Printed myData Array
Array[<RivosTaxi.Favoritos: 0x7fb46ff5ac80> (entity: Favoritos; id: 0xd00000000024000e <x-coredata://82CCF746-275D-4FC6-9C5C-EFD1EDED2F21/Favoritos/p9> ; data: {
"desc_place" = nil;
latitude = nil;
longitude = nil;
"place_favorite_id" = nil;
"place_name" = nil;
}), <RivosTaxi.Favoritos: 0x7fb46ff5af80> (entity: Favoritos; id: 0xd00000000028000e <x-coredata://82CCF746-275D-4FC6-9C5C-EFD1EDED2F21/Favoritos/p10> ; data: {
"desc_place" = "";
latitude = "24.822311";
longitude = "-107.4240634";
"place_favorite_id" = 94;
"place_name" = "Otro mad";
That is what I dont understand, the first values are all nil, and I only have 1 row of data at my sqlite, not two
This is how y save it to Favoritos Entity
func FavoritosSave(){
let entity = NSEntityDescription.insertNewObjectForEntityForName("Favoritos", inManagedObjectContext: moc)
entity.setValue(place_name, forKey: "place_name")
entity.setValue(place_favorite, forKey: "place_favorite_id")
entity.setValue(desc_place, forKey: "desc_place")
entity.setValue(latitude, forKey: "latitude")
entity.setValue(longitude, forKey: "longitude")
// we save our entity
do {
try moc.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Your fetch request is written to return all instances with entity name of "Favoritos". That search result shows that you've created two.
An explanation for only seeing one row could be that your MOC hasn't been saved. But going dumpster diving in Core Data's SQLite file is a recipe for frustration. Use the documented APIs.
You should add a call to fetchFavoritos() as the first line of your save() function, to see what's there before you create the Favoritos instance.
How can I use UserDefaults to save/retrieve strings, booleans and other data in Swift?
ref: NSUserdefault objectTypes
Swift 3 and above
Store
UserDefaults.standard.set(true, forKey: "Key") //Bool
UserDefaults.standard.set(1, forKey: "Key") //Integer
UserDefaults.standard.set("TEST", forKey: "Key") //setObject
Retrieve
UserDefaults.standard.bool(forKey: "Key")
UserDefaults.standard.integer(forKey: "Key")
UserDefaults.standard.string(forKey: "Key")
Remove
UserDefaults.standard.removeObject(forKey: "Key")
Remove all Keys
if let appDomain = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: appDomain)
}
Swift 2 and below
Store
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: "yourkey")
NSUserDefaults.standardUserDefaults().synchronize()
Retrieve
var returnValue: [NSString]? = NSUserDefaults.standardUserDefaults().objectForKey("yourkey") as? [NSString]
Remove
NSUserDefaults.standardUserDefaults().removeObjectForKey("yourkey")
Register
registerDefaults: adds the registrationDictionary to the last item in every search list. This means that after NSUserDefaults has looked for a value in every other valid location, it will look in registered defaults, making them useful as a "fallback" value. Registered defaults are never stored between runs of an application, and are visible only to the application that registers them.
Default values from Defaults Configuration Files will automatically be registered.
for example detect the app from launch , create the struct for save launch
struct DetectLaunch {
static let keyforLaunch = "validateFirstlunch"
static var isFirst: Bool {
get {
return UserDefaults.standard.bool(forKey: keyforLaunch)
}
set {
UserDefaults.standard.set(newValue, forKey: keyforLaunch)
}
}
}
Register default values on app launch:
UserDefaults.standard.register(defaults: [
DetectLaunch.isFirst: true
])
remove the value on app termination:
func applicationWillTerminate(_ application: UIApplication) {
DetectLaunch.isFirst = false
}
and check the condition as
if DetectLaunch.isFirst {
// app launched from first
}
UserDefaults suite name
another one property suite name, mostly its used for App Groups concept, the example scenario I taken from here :
The use case is that I want to separate my UserDefaults (different business logic may require Userdefaults to be grouped separately) by an identifier just like Android's SharedPreferences. For example, when a user in my app clicks on logout button, I would want to clear his account related defaults but not location of the the device.
let user = UserDefaults(suiteName:"User")
use of userDefaults synchronize, the detail info has added in the duplicate answer.
Best way to use UserDefaults
Steps
Create extension of UserDefaults
Create enum with required Keys to
store in local
Store and retrieve the local data wherever you want
Sample
extension UserDefaults{
//MARK: Check Login
func setLoggedIn(value: Bool) {
set(value, forKey: UserDefaultsKeys.isLoggedIn.rawValue)
//synchronize()
}
func isLoggedIn()-> Bool {
return bool(forKey: UserDefaultsKeys.isLoggedIn.rawValue)
}
//MARK: Save User Data
func setUserID(value: Int){
set(value, forKey: UserDefaultsKeys.userID.rawValue)
//synchronize()
}
//MARK: Retrieve User Data
func getUserID() -> Int{
return integer(forKey: UserDefaultsKeys.userID.rawValue)
}
}
enum for Keys used to store data
enum UserDefaultsKeys : String {
case isLoggedIn
case userID
}
Save in UserDefaults where you want
UserDefaults.standard.setLoggedIn(value: true) // String
UserDefaults.standard.setUserID(value: result.User.id!) // String
Retrieve data anywhere in app
print("ID : \(UserDefaults.standard.getUserID())")
UserDefaults.standard.getUserID()
Remove Values
UserDefaults.standard.removeObject(forKey: UserDefaultsKeys.userID)
This way you can store primitive data in best
Update
You need no use synchronize() to store the values. As #Moritz pointed out the it unnecessary and given the article about it.Check comments for more detail
Swift 4 :
Store
UserDefaults.standard.set(object/value, forKey: "key_name")
Retrive
var returnValue: [datatype]? = UserDefaults.standard.object(forKey: "key_name") as? [datatype]
Remove
UserDefaults.standard.removeObject(forKey:"key_name")
UserDefault+Helper.swift
import UIKit
private enum Defaults: String {
case countryCode = "countryCode"
case userloginId = "userloginid"
}
final class UserDefaultHelper {
static var countryCode: String? {
set{
_set(value: newValue, key: .countryCode)
} get {
return _get(valueForKay: .countryCode) as? String ?? ""
}
}
static var userloginId: String? {
set{
_set(value: newValue, key: .userloginId)
} get {
return _get(valueForKay: .userloginId) as? String ?? ""
}
}
private static func _set(value: Any?, key: Defaults) {
UserDefaults.standard.set(value, forKey: key.rawValue)
}
private static func _get(valueForKay key: Defaults)-> Any? {
return UserDefaults.standard.value(forKey: key.rawValue)
}
static func deleteCountryCode() {
UserDefaults.standard.removeObject(forKey: Defaults.countryCode.rawValue)
}
static func deleteUserLoginId() {
UserDefaults.standard.removeObject(forKey: Defaults.userloginId.rawValue)
}
}
Usage:
Save Value:
UserDefaultHelper.userloginId = data["user_id"] as? String
Fetch Value:
let userloginid = UserDefaultHelper.userloginId
Delete Value:
UserDefaultHelper.deleteUserLoginId()
I would say Anbu's answer perfectly fine but I had to add guard while fetching preferences to make my program doesn't fail
Here is the updated code snip in Swift 5
Storing data in UserDefaults
#IBAction func savePreferenceData(_ sender: Any) {
print("Storing data..")
UserDefaults.standard.set("RDC", forKey: "UserName") //String
UserDefaults.standard.set("TestPass", forKey: "Passowrd") //String
UserDefaults.standard.set(21, forKey: "Age") //Integer
}
Fetching data from UserDefaults
#IBAction func fetchPreferenceData(_ sender: Any) {
print("Fetching data..")
//added guard
guard let uName = UserDefaults.standard.string(forKey: "UserName") else { return }
print("User Name is :"+uName)
print(UserDefaults.standard.integer(forKey: "Age"))
}
//Save
NSUserDefaults.standardUserDefaults().setObject("yourString", forKey: "YourStringKey")
//retrive
let yourStr : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("YourStringKey")
You can use NSUserDefaults in swift this way,
#IBAction func writeButton(sender: UIButton)
{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("defaultvalue", forKey: "userNameKey")
}
#IBAction func readButton(sender: UIButton)
{
let defaults = NSUserDefaults.standardUserDefaults()
let name = defaults.stringForKey("userNameKey")
println(name) //Prints defaultvalue in console
}
Swift 5 and above:
let defaults = UserDefaults.standard
defaults.set(25, forKey: "Age")
let savedInteger = defaults.integer(forKey: "Age")
defaults.set(true, forKey: "UseFaceID")
let savedBoolean = defaults.bool(forKey: "UseFaceID")
defaults.set(CGFloat.pi, forKey: "Pi")
defaults.set("Your Name", forKey: "Name")
defaults.set(Date(), forKey: "LastRun")
let array = ["Hello", "World"]
defaults.set(array, forKey: "SavedArray")
let savedArray = defaults.object(forKey: "SavedArray") as? [String] ?? [String()
let dict = ["Name": "Your", "Country": "YourCountry"]
defaults.set(dict, forKey: "SavedDict")
let savedDictionary = defaults.object(forKey: "SavedDictionary") as? [String: String] ?? [String: String]()
:)
I saved NSDictionary normally and able to get it correctly.
dictForaddress = placemark.addressDictionary! as NSDictionary
let userDefaults = UserDefaults.standard
userDefaults.set(dictForaddress, forKey:Constants.kAddressOfUser)
// For getting data from NSDictionary.
let userDefaults = UserDefaults.standard
let dictAddress = userDefaults.object(forKey: Constants.kAddressOfUser) as! NSDictionary
I have Created my Custom Functions for Store Data in Userdefualts
//******************* REMOVE NSUSER DEFAULT *******************
func removeUserDefault(key:String) {
UserDefaults.standard.removeObject(forKey: key);
}
//******************* SAVE STRING IN USER DEFAULT *******************
func saveInDefault(value:Any,key:String) {
UserDefaults.standard.setValue(value, forKey: key);
UserDefaults.standard.synchronize();
}
//******************* FETCH STRING FROM USER DEFAULT *******************
func fetchString(key:String)->AnyObject {
if (UserDefaults.standard.object(forKey: key) != nil) {
return UserDefaults.standard.value(forKey: key)! as AnyObject;
}
else {
return "" as AnyObject;
}
}
class UserDefaults_FavoriteQuote {
static let key = "appname.favoriteQuote"
static var value: String? {
get {
return UserDefaults.standard.string(forKey: key)
}
set {
if newValue != nil {
UserDefaults.standard.set(newValue, forKey: key)
} else {
UserDefaults.standard.removeObject(forKey: key)
}
}
}
}
In class A, set value for key:
let text = "hai"
UserDefaults.standard.setValue(text, forKey: "textValue")
In class B, get the value for the text using the key which declared in class A and assign it to respective variable which you need:
var valueOfText = UserDefaults.value(forKey: "textValue")
Swift 4,
I have used Enum for handling UserDefaults.
This is just a sample code. You can customize it as per your requirements.
For Storing, Retrieving, Removing.
In this way just add a key for your UserDefaults key to the enum.
Handle values while getting and storing according to dataType and your requirements.
enum UserDefaultsConstant : String {
case AuthToken, FcmToken
static let defaults = UserDefaults.standard
//Store
func setValue(value : Any) {
switch self {
case .AuthToken,.FcmToken:
if let _ = value as? String {
UserDefaults.standard.set(value, forKey: self.rawValue)
}
break
}
UserDefaults.standard.synchronize()
}
//Retrieve
func getValue() -> Any? {
switch self {
case .AuthToken:
if(UserDefaults.standard.value(forKey: UserDefaultsConstant.AuthToken.rawValue) != nil) {
return "Bearer "+(UserDefaults.standard.value(forKey: UserDefaultsConstant.AuthToken.rawValue) as! String)
}
else {
return ""
}
case .FcmToken:
if(UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue) != nil) {
print(UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue))
return (UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue) as! String)
}
else {
return ""
}
}
}
//Remove
func removeValue() {
UserDefaults.standard.removeObject(forKey: self.rawValue)
UserDefaults.standard.synchronize()
}
}
For storing a value in userdefaults,
if let authToken = resp.data?.token {
UserDefaultsConstant.AuthToken.setValue(value: authToken)
}
For retrieving a value from userdefaults,
//As AuthToken value is a string
(UserDefaultsConstant.AuthToken.getValue() as! String)
use UserDefault to store any settings value you want your application to remember between start ups, maybe you want to know ifs its been started before, maybe you want some values the user has set to be remembers so they don't have to be set very time, on Mac windows frames are stored in there for you, maybe you want to control the behaviour of the app, but you don't want it available to end users, you just want to choose just before your release. Be careful what you store in UserDefaults, it's not protected.
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.