NSKeyedUnarchiver can't decode some of my keys - swift

I have some keys I want to decode, but it only decodes my first string and then it gives error.
class Profile: NSCoder, NSCoding {
struct Constants {
// Age
static let minimumAge: Int = 5
static let maximumAge: Int = 120
static let defaultAge: Int = 25
// Gender
static let defaultGender: Int = 0
// Daily meals
static let defaultDailyMeals: Int = 3
static let minimumDailyMeals: Int = 1
static let maximumDailyMeals: Int = 10
// Do you train
static let defaultIsTraining: Bool = false
}
//MARK: Properties
private var name: String
private var age: Int
private var gender: Int
private var target: Int
private var dailyMeals: Int
private var isTraining: Bool
private var trainingWhen: Int?
private var trainingDays: [(Int)];
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("profile")
//MARK: Types
struct PropertyKey {
static let name = "profileName"
static let age = "profileAge"
static let gender = "profileGender"
static let target = "profileTarget"
static let dailyMeals = "profileDailyMeals"
static let isTraining = "profileIsTraining"
static let trainingWhen = "profileTrainingWhen"
static let trainingDays = "trainingDays"
}
// Main init
init?(name: String, age: Int, gender: Int, target: Int, dailyMeals: Int, isTraining: Bool, trainingWhen: Int?, trainingDays: [(Int)]) {
guard !name.isEmpty else {
print("Name empty")
return nil
}
guard age > Constants.minimumAge && age < Constants.maximumAge else {
print("Age not in range")
return nil
}
self.name = name
self.age = age
self.gender = gender
self.target = target
self.dailyMeals = dailyMeals
self.isTraining = isTraining
self.trainingWhen = trainingWhen
self.trainingDays = trainingDays
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: PropertyKey.name)
aCoder.encode(self.age, forKey: PropertyKey.age)
aCoder.encode(self.gender, forKey: PropertyKey.gender)
aCoder.encode(self.target, forKey: PropertyKey.target)
aCoder.encode(self.dailyMeals, forKey: PropertyKey.dailyMeals)
aCoder.encode(self.isTraining, forKey: PropertyKey.isTraining)
aCoder.encode(self.trainingWhen, forKey: PropertyKey.trainingWhen)
aCoder.encode(self.trainingDays, forKey: PropertyKey.trainingDays)
}
func dump_data(){
print("Name: " + self.name)
print("Age: " + String(self.age))
print("Gender: " + String(self.gender))
print("Target: " + String(self.target))
print("Daily Meals: " + String(self.dailyMeals))
print("Does train?: " + String(self.isTraining))
print("When: " + String(describing: self.trainingWhen))
print("Days: " + String(describing: self.trainingDays))
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
print("Unable to decode the name for a Profile object.")
return nil
}
print("Decoded: Name - \(name)")
guard let age = aDecoder.decodeObject(forKey: PropertyKey.age) as? Int else {
print("Unable to decode the age for a Profile object.")
return nil
}
guard let gender = aDecoder.decodeObject(forKey: PropertyKey.gender) as? Int else {
print("Unable to decode the gender for a Profile object.")
return nil
}
guard let target = aDecoder.decodeObject(forKey: PropertyKey.target) as? Int else {
print("Unable to decode the target for a Profile object.")
return nil
}
guard let dailyMeals = aDecoder.decodeObject(forKey: PropertyKey.dailyMeals) as? Int else {
print("Unable to decode the daily meals for a Profile object.")
return nil
}
guard let isTraining = aDecoder.decodeObject(forKey: PropertyKey.isTraining) as? Bool else {
print("Unable to decode the training state for a Profile object.")
return nil
}
let trainingWhen: Int? = aDecoder.decodeObject(forKey: PropertyKey.trainingWhen) as? Int ?? nil
let trainingDays: [(Int)] = aDecoder.decodeObject(forKey: PropertyKey.trainingDays) as? [(Int)] ?? []
// Must call designated initializer.
self.init(name: name, age: age, gender: gender, target: target, dailyMeals: dailyMeals, isTraining: isTraining, trainingWhen: trainingWhen, trainingDays: trainingDays)
}
The error message I get is
Unable to decode the age for a Profile object.
As long as I know this means that my aDecoder.decodeObject(forKey: PropertyKey.gender) as? Int returns nil and I have completely no idea why this happens, because I encode it as you can see in the code. :/

You should probably use the decodeInteger(forKey:_) method, like this:
self.age = aDecoder.decodeInteger(forKey: PropertyKey.age)
With that said, what I usually do in these cases (considering you only need one instance of the class) is this:
//: Playground - noun: a place where people can play
import UIKit
class Profile: NSCoder, NSCoding {
struct Constants {
// Age
static let minimumAge: Int = 5
static let maximumAge: Int = 120
static let defaultAge: Int = 25
// Gender
static let defaultGender: Int = 0
// Daily meals
static let defaultDailyMeals: Int = 3
static let minimumDailyMeals: Int = 1
static let maximumDailyMeals: Int = 10
// Do you train
static let defaultIsTraining: Bool = false
}
//MARK: Properties
var name: String = ""
var age: Int = 0
var gender: Int = 0
var target: Int = 0
var dailyMeals: Int = 0
var isTraining: Bool = false
var trainingWhen: Int? = nil
var trainingDays = [Int]();
//MARK: Archiving Paths
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path
static let fileName = "profile"
static let path = "\(Profile.documentsDirectory)/\(fileName)"
//MARK: Types
struct PropertyKey {
static let name = "profileName"
static let age = "profileAge"
static let gender = "profileGender"
static let target = "profileTarget"
static let dailyMeals = "profileDailyMeals"
static let isTraining = "profileIsTraining"
static let trainingWhen = "profileTrainingWhen"
static let trainingDays = "trainingDays"
}
// Main init
static var shared = Profile()
fileprivate override init() { }
//MARK: NSCoding
func encode(with archiver: NSCoder) {
archiver.encode(self.name, forKey: PropertyKey.name)
archiver.encode(self.age, forKey: PropertyKey.age)
archiver.encode(self.gender, forKey: PropertyKey.gender)
archiver.encode(self.target, forKey: PropertyKey.target)
archiver.encode(self.dailyMeals, forKey: PropertyKey.dailyMeals)
archiver.encode(self.isTraining, forKey: PropertyKey.isTraining)
archiver.encode(self.trainingWhen, forKey: PropertyKey.trainingWhen)
archiver.encode(self.trainingDays, forKey: PropertyKey.trainingDays)
}
override var description: String {
return """
Name: \(self.name)
Age: \(self.age)
Gender: \(self.gender)
Target: \(self.target)
Daily Meals: \(self.dailyMeals)
Does train?: \(self.isTraining)
When: \(self.trainingWhen ?? 0)
Days: \(self.trainingDays)
"""
}
func save() -> Bool {
return NSKeyedArchiver.archiveRootObject(self, toFile: Profile.path)
}
required init(coder unarchiver: NSCoder) {
super.init()
if let name = unarchiver.decodeObject(forKey: PropertyKey.name) as? String {
self.name = name
}
self.age = unarchiver.decodeInteger(forKey: PropertyKey.age)
self.gender = unarchiver.decodeInteger(forKey: PropertyKey.gender)
self.target = unarchiver.decodeInteger(forKey: PropertyKey.target)
self.dailyMeals = unarchiver.decodeInteger(forKey: PropertyKey.dailyMeals)
self.isTraining = unarchiver.decodeBool(forKey: PropertyKey.isTraining)
if let trainingWhen = unarchiver.decodeObject(forKey: PropertyKey.trainingWhen) as? Int {
self.trainingWhen = trainingWhen
}
if let trainingDays = unarchiver.decodeObject(forKey: PropertyKey.trainingDays) as? [Int] {
self.trainingDays = trainingDays
}
}
}
// TEST:
Profile.shared.name = "Daniel"
Profile.shared.age = 10
Profile.shared.gender = 30
Profile.shared.target = 10
Profile.shared.dailyMeals = 90
Profile.shared.isTraining = true
Profile.shared.trainingWhen = 20
Profile.shared.trainingDays = [1,2,5]
Profile.shared.save()
if let profile = NSKeyedUnarchiver.unarchiveObject(withFile: Profile.path) as? Profile {
print(profile.description)
}
/*
Output:
--------
Name: Daniel
Age: 10
Gender: 30
Target: 10
Daily Meals: 90
Does train?: true
When: 20
Days: [1, 2, 5]
*/

Related

NSKeyedUnarchiver.unarchiveTopLevelObjectWithData return nil

I want to save an array of objects into UserDefaults and load it back.
When trying to unarchive the data it always returns nil.. any idea?
This is my object:
class DbInsideLevel: NSObject, NSSecureCoding {
static var supportsSecureCoding: Bool {
return true
}
let idNum: Int!
var topicId: Int = 0
var tryCount: Int = 0
var score: Int = 0
var isOpen: Bool = false
var lastPlayedDate: Date?
init(idNum: Int, topicId: Int, tryCount: Int = 0, score: Int = 0, open: Bool, lastPlayedDate: Date?) {
self.idNum = idNum
self.topicId = topicId
self.tryCount = tryCount
self.score = score
self.isOpen = open
self.lastPlayedDate = lastPlayedDate
}
convenience required init?(coder: NSCoder) {
guard
let idNum = coder.decodeObject(forKey: "idNum") as? Int,
let topicId = coder.decodeObject(forKey: "topicId") as? Int,
let tryCount = coder.decodeObject(forKey: "tryCount") as? Int,
let score = coder.decodeObject(forKey: "score") as? Int,
let open = coder.decodeObject(forKey: "isOpen") as? Bool,
let lastPlayed = coder.decodeObject(forKey: "lastPlayedDate") as? Date
else {
return nil
}
self.init(idNum: idNum, topicId: topicId, tryCount: tryCount, score: score, open: open, lastPlayedDate: lastPlayed)
}
func encode(with coder: NSCoder) {
coder.encode(idNum, forKey: "idNum")
coder.encode(topicId, forKey: "topicId")
coder.encode(tryCount, forKey: "tryCount")
coder.encode(score, forKey: "score")
coder.encode(isOpen, forKey: "isOpen")
coder.encode(lastPlayedDate, forKey: "lastPlayedDate")
}
func update(score: Int) {
// Update score if necessary
if score > self.score {
self.score = score
}
// Increment try count
self.tryCount = self.tryCount + 1
}
}
Archiving the data:
func archiveData(with object: Any, to key: String) -> Data? {
do {
guard
let data = try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true) else {
return nil
}
return data
}
}
Unarchiving the data:
func unarchiveData(data: Data) -> Any? {
do {
let unarchivedData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [DbInsideLevel]
return unarchivedData
} catch {
return nil
}
}
Saving to UserDefaults:
class func americanSaveContext(data: [DbInsideLevel]) {
if data.count != 0 {
if let archived = MainDb.sharedInstance.archiveData(with: data, to: AppConstants.Keys.UserDefaults.American) {
MainDb.sharedInstance.dataStore(data: archived)
}
}
}
Loading from UserDefaults:
class func americanDataRetrive(topicId: Int) -> [DbInsideLevel]? {
if let data = MainDb.sharedInstance.dataRetrive() {
let unarchived = MainDb.sharedInstance.unarchiveData(data: data) as! [DbInsideLevel]
return unarchived
}
return nil
}
UserDefaults helpers for saving / loading:
extension MainDb {
// MARK: - UserDefaults helpers
func dataRetrive() -> Data? {
let defaults = UserDefaults.standard
return defaults.value(forKey: AppConstants.Keys.UserDefaults.American) as? Data
}
func dataStore(data: Data) {
let defaults = UserDefaults.standard
defaults.setValue(data, forKey: AppConstants.Keys.UserDefaults.American)
}
func dataReset() {
let defaults = UserDefaults.standard
defaults.removeObject(forKey: AppConstants.Keys.UserDefaults.American)
}
}
Your issue there is that you are using the wrong method to decode your integers and your boolean. I would also implement the required decoder instead of a convenience initializer.
required init(coder decoder: NSCoder) {
self.idNum = decoder.decodeInteger(forKey: "idNum")
self.topicId = decoder.decodeInteger(forKey: "topicId")
self.tryCount = decoder.decodeInteger(forKey: "tryCount")
self.score = decoder.decodeInteger(forKey: "score")
self.isOpen = decoder.decodeBool(forKey: "isOpen")
self.lastPlayedDate = decoder.decodeObject(forKey: "lastPlayedDate") as? Date
}
Note that you can also use Swift 4 or later Codable protocol to encode and decode your custom classes/structures and save them as data inside UserDefaults or as a plist file inside your application support folder. Last but not least don't use setValue and value(forKey:) to save and retrieve your data, user defaults has a specific method for retrieving data called data(forKey:) and set(_ value: Any) to persist your data:
extension UserDefaults {
func decode<T: Decodable>(_ type: T.Type, forKey defaultName: String) throws -> T {
try JSONDecoder().decode(T.self, from: data(forKey: defaultName) ?? .init())
}
func encode<T: Encodable>(_ value: T, forKey defaultName: String) throws {
try set(JSONEncoder().encode(value), forKey: defaultName)
}
}
class DbInsideLevel: Codable {
let idNum: Int!
var topicId: Int = 0
var tryCount: Int = 0
var score: Int = 0
var isOpen: Bool = false
var lastPlayedDate: Date?
init(idNum: Int, topicId: Int, tryCount: Int = 0, score: Int = 0, open: Bool, lastPlayedDate: Date?) {
self.idNum = idNum
self.topicId = topicId
self.tryCount = tryCount
self.score = score
self.isOpen = open
self.lastPlayedDate = lastPlayedDate
}
func update(score: Int) {
if score > self.score {
self.score = score
}
self.tryCount = self.tryCount + 1
}
}
Playground testing
let insideLevel = DbInsideLevel(idNum: 1, topicId: 2, tryCount: 3, score: 4, open: true, lastPlayedDate: Date())
do {
try UserDefaults.standard.encode(insideLevel, forKey: "insideLevel")
let decodedLevel = try UserDefaults.standard.decode(DbInsideLevel.self, forKey: "insideLevel")
print("decodedLevel idNum", decodedLevel.idNum) // decodedLevel idNum Optional(1)
} catch {
print(error)
}
edit/update:
It does work for arrays of Codable types as well:
let insideLevel = DbInsideLevel(idNum: 1, topicId: 2, tryCount: 3, score: 4, open: true, lastPlayedDate: Date())
do {
try UserDefaults.standard.encode([insideLevel], forKey: "insideLevels")
let decodedLevel = try UserDefaults.standard.decode([DbInsideLevel].self, forKey: "insideLevels")
print("decodedLevel idNum", decodedLevel.first?.idNum) // decodedLevel idNum Optional(1)
} catch {
print(error)
}

Swift – macOS `batteryLevel` property

In iOS there's a simple approach to get a level of iPhone battery:
UIDevice.current.batteryLevel
What's a macOS batteryLevel equivalent for MacBooks?
Getting the battery level in macOS is not as easy as in iOS, but it is still pretty easy. You have to use the IOKit framework for this.
You can use this code to read almost all of the important information from the internal battery.
InternalBattery.swift
import Foundation
import IOKit.ps
public class InternalBattery {
public var name: String?
public var timeToFull: Int?
public var timeToEmpty: Int?
public var manufacturer: String?
public var manufactureDate: Date?
public var currentCapacity: Int?
public var maxCapacity: Int?
public var designCapacity: Int?
public var cycleCount: Int?
public var designCycleCount: Int?
public var acPowered: Bool?
public var isCharging: Bool?
public var isCharged: Bool?
public var amperage: Int?
public var voltage: Double?
public var watts: Double?
public var temperature: Double?
public var charge: Double? {
get {
if let current = self.currentCapacity,
let max = self.maxCapacity {
return (Double(current) / Double(max)) * 100.0
}
return nil
}
}
public var health: Double? {
get {
if let design = self.designCapacity,
let current = self.maxCapacity {
return (Double(current) / Double(design)) * 100.0
}
return nil
}
}
public var timeLeft: String {
get {
if let isCharging = self.isCharging {
if let minutes = isCharging ? self.timeToFull : self.timeToEmpty {
if minutes <= 0 {
return "-"
}
return String(format: "%.2d:%.2d", minutes / 60, minutes % 60)
}
}
return "-"
}
}
public var timeRemaining: Int? {
get {
if let isCharging = self.isCharging {
return isCharging ? self.timeToFull : self.timeToEmpty
}
return nil
}
}
}
InternalFinder.swift:
import Foundation
import IOKit.ps
public class InternalFinder {
private var serviceInternal: io_connect_t = 0 // io_object_t
private var internalChecked: Bool = false
private var hasInternalBattery: Bool = false
public init() { }
public var batteryPresent: Bool {
get {
if !self.internalChecked {
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
self.hasInternalBattery = sources.count > 0
self.internalChecked = true
}
return self.hasInternalBattery
}
}
fileprivate func open() {
self.serviceInternal = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
}
fileprivate func close() {
IOServiceClose(self.serviceInternal)
IOObjectRelease(self.serviceInternal)
self.serviceInternal = 0
}
public func getInternalBattery() -> InternalBattery? {
self.open()
if self.serviceInternal == 0 {
return nil
}
let battery = self.getBatteryData()
self.close()
return battery
}
fileprivate func getBatteryData() -> InternalBattery {
let battery = InternalBattery()
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
for ps in sources {
// Fetch the information for a given power source out of our snapshot
let info = IOPSGetPowerSourceDescription(snapshot, ps).takeUnretainedValue() as! Dictionary<String, Any>
// Pull out the name and capacity
battery.name = info[kIOPSNameKey] as? String
battery.timeToEmpty = info[kIOPSTimeToEmptyKey] as? Int
battery.timeToFull = info[kIOPSTimeToFullChargeKey] as? Int
}
// Capacities
battery.currentCapacity = self.getIntValue("CurrentCapacity" as CFString)
battery.maxCapacity = self.getIntValue("MaxCapacity" as CFString)
battery.designCapacity = self.getIntValue("DesignCapacity" as CFString)
// Battery Cycles
battery.cycleCount = self.getIntValue("CycleCount" as CFString)
battery.designCycleCount = self.getIntValue("DesignCycleCount9C" as CFString)
// Plug
battery.acPowered = self.getBoolValue("ExternalConnected" as CFString)
battery.isCharging = self.getBoolValue("IsCharging" as CFString)
battery.isCharged = self.getBoolValue("FullyCharged" as CFString)
// Power
battery.amperage = self.getIntValue("Amperage" as CFString)
battery.voltage = self.getVoltage()
// Various
battery.temperature = self.getTemperature()
// Manufaction
battery.manufacturer = self.getStringValue("Manufacturer" as CFString)
battery.manufactureDate = self.getManufactureDate()
if let amperage = battery.amperage,
let volts = battery.voltage, let isCharging = battery.isCharging {
let factor: CGFloat = isCharging ? 1 : -1
let watts: CGFloat = (CGFloat(amperage) * CGFloat(volts)) / 1000.0 * factor
battery.watts = Double(watts)
}
return battery
}
fileprivate func getIntValue(_ identifier: CFString) -> Int? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Int
}
return nil
}
fileprivate func getStringValue(_ identifier: CFString) -> String? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? String
}
return nil
}
fileprivate func getBoolValue(_ forIdentifier: CFString) -> Bool? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, forIdentifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Bool
}
return nil
}
fileprivate func getTemperature() -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, "Temperature" as CFString, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as! Double / 100.0
}
return nil
}
fileprivate func getDoubleValue(_ identifier: CFString) -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Double
}
return nil
}
fileprivate func getVoltage() -> Double? {
if let value = getDoubleValue("Voltage" as CFString) {
return value / 1000.0
}
return nil
}
fileprivate func getManufactureDate() -> Date? {
if let value = IORegistryEntryCreateCFProperty(self.serviceInternal, "ManufactureDate" as CFString, kCFAllocatorDefault, 0) {
let date = value.takeRetainedValue() as! Int
let day = date & 31
let month = (date >> 5) & 15
let year = ((date >> 9) & 127) + 1980
var components = DateComponents()
components.calendar = Calendar.current
components.day = day
components.month = month
components.year = year
return components.date
}
return nil
}
}
Usage:
let internalFinder = InternalFinder()
if let internalBattery = internalFinder.getInternalBattery() {
// Internal battery found, access properties here.
}

How to Clear Shared Dictionary which is causing saved values not to clear even when I login with other user

How can I clear the shared dictionary on logout in which I am saving login response?
Here is the code I am doing on getting status 1.
if(status == 1)
{
DispatchQueue.main.async {
GAReusableClass.sharedInstance.hideActivityIndicator()
UserDefaults.standard.set(self.DataDict, forKey:MaindataKey)
let Dict = self.mainDict[KData] as! [String: AnyObject]
print("self.DataDict", self.DataDict)
let User_ID = Dict[KUuid]as! String
print(User_ID)
let HMACSECRETKEY = self.deviceToken + "+" + User_ID
kHMACKey = HMACSECRETKEY
let cipher:String = CryptoHelper.encrypt(input:HMACSECRETKEY)!;
print(HMACSECRETKEY)
UserDefaults.standard.setValue(cipher, forKey:HmacKey)
UserDefaults.standard.set(true, forKey: "isLogin")
GAloginUserInfo.shared.saveUserInfo(dict: Dict )
let tabar = self.storyboard?.instantiateViewController(withIdentifier: "GAtHomeTabbarViewController") as! GAtHomeTabbarViewController
self.navigationController?.pushViewController(tabar, animated: true)
}
Here is the shared dictionary which I am using to save the values of login response.
import UIKit
import Firebase
class GAloginUserInfo: NSObject {
var loginUserMobileNo : String?
var loginUserId : String?
var loginUserUuid : String?
var loginUserCountry : String?
var loginUserCountryCode : String?
var loginUserEmail : String?
var loginUserlatitude : String?
var loginUserLongitude : String?
var loginUserName : String?
var loginUserQrcode : String?
var loginUserProfilePic : String?
var isverify : String?
var loginPassword : String?
var dateOfBirth: String?
var earnedPoints:String?
var loginUserGender:String?
var loginUserFollowers:Int = 0
static let shared = GAloginUserInfo()
func saveUserInfo (dict : [String : AnyObject?] ) {
if let loginUserMobileNo = dict["mobile"] as? String {
self.loginUserMobileNo = loginUserMobileNo
}
if let loginUserId = dict["id"] as? String {
self.loginUserId = loginUserId
}
if let loginUserUuid = dict["uuid"] as? String {
self.loginUserUuid = loginUserUuid
print(loginUserUuid)
}
if let loginUserCountry = dict["country"] as? String {
self.loginUserCountry = loginUserCountry
}
if let loginUserCountryCode = dict["country_code"] as? String {
self.loginUserCountryCode = loginUserCountryCode
}
if let loginUserEmail = dict["email"] as? String {
self.loginUserEmail = loginUserEmail
}
if let loginUserProfilePic = dict["profile_pic"] as? String {
self.loginUserProfilePic = loginUserProfilePic
}
if let loginUserLongitude = dict["logitude"] as? String {
self.loginUserLongitude = loginUserLongitude
}
if let loginUserName = dict["name"] as? String {
self.loginUserName = loginUserName
}
if let loginUserQrcode = dict["qr_code"] as? String {
self.loginUserQrcode = loginUserQrcode
}
if let Password = dict["password"] as? String{
self.loginPassword = Password
}
if let dateOfBirth = dict["dob"] as? String{
self.dateOfBirth = dateOfBirth
}
if let earnedPoints = dict["points"] as? String{
let myDouble = Double(earnedPoints)
let doubleStr = String(format: "%.2f", myDouble!)
self.earnedPoints = doubleStr
}
if let loginUserGender = dict["gender"] as? String{
self.loginUserGender = loginUserGender
}
if let loginUserFollowers = dict["followersCount"] as? Int{
self.loginUserFollowers = loginUserFollowers
}
}
}
Actually, the problem is when I log out and log in again with some other user it still shows some values of the previous user. I am clearing the userdefaults on the logout function. but I don't know how to clear this type of shared dictionary.
Use removeObject(forKey:)
to remove the values stored from user defaults in Logout method
UserDefaults.standard.removeObject(forKey: MaindataKey)
UserDefaults.standard.removeObject(forKey: HmacKey)
UserDefaults.standard.set(false, forKey: "isLogin")
Create a method to remove the values from the singleton class like this
extension GAloginUserInfo {
func removeUserInfo() {
self.loginUserMobileNo = nil
self.loginUserId = nil
self.loginUserUuid = nil
self.loginUserCountry = nil
self.loginUserCountryCode = nil
self.loginUserEmail = nil
self.loginUserlatitude = nil
self.loginUserLongitude = nil
self.loginUserName = nil
self.loginUserQrcode = nil
self.loginUserProfilePic = nil
self.isverify = nil
self.loginPassword = nil
self.dateOfBirth = nil
self.earnedPoints = nil
self.loginUserGender = nil
self.loginUserFollowers = 0
}
}
and call this method in logout
GAloginUserInfo.shared.removeUserInfo()

Getting optional String from singleton

I've created a class with some vars and lets. One of these vars is a String. I store them in UserDefaults. If I want to access the string of this class over a singleton class, I will always get an optional String. I don't know why.
Here is the class of the object:
import Foundation
import SpriteKit
class BallSkinsClass: NSObject, NSCoding {
let id: Int
var name: String
var isBuyed: Bool
let ID = "id"
let NAME = "name"
let ISBUYED = "isBuyed"
init(id: Int, name: String, isBuyed: Bool) {
self.id = id
self.name = name
self.isBuyed = isBuyed
}
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
#objc func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: ID)
aCoder.encode(name, forKey: NAME)
aCoder.encode(isBuyed, forKey: ISBUYED)
}
}
To declare the skins, access, save and load I have these functions in my BallSkinsClass:
import Foundation
import SpriteKit
import GameKit
class BallSkins {
static var sharedInstance = BallSkins()
private init() {
}
let BALLSKINS = "ballSkins"
var standard: BallSkinsClass! = BallSkinsClass(id: 0, name: "Standard", isBuyed: true)
var billiard: BallSkinsClass! = BallSkinsClass(id: 1, name: "Billard", isBuyed: false)
var emoji: BallSkinsClass! = BallSkinsClass(id: 2, name: "Emojis", isBuyed: false)
func archiveBallSkins(ballSkins:[BallSkinsClass]) -> NSData {
print("archiving Skins")
let archivedBallSkins = NSKeyedArchiver.archivedData(withRootObject: ballSkins as Array)
return archivedBallSkins as NSData
}
func saveBallSkins(ballSkins:[BallSkinsClass]) {
let archivedBallSkins = archiveBallSkins(ballSkins: ballSkins)
UserDefaults.standard.set(archivedBallSkins, forKey: BALLSKINS)
print("saving Skins")
}
func retrieveBallSkins() -> [BallSkinsClass]? {
print("retrieving Skins")
if let unarchivedBallSkins = UserDefaults.standard.object(forKey: BALLSKINS) as? NSData {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedBallSkins as Data) as? [BallSkinsClass]
}
return nil
}
func loadBallSkins() {
print("loading Skins")
let archivedBallSkins = retrieveBallSkins()
for ballSkin in archivedBallSkins! {
switch ballSkin.id {
case 0 :
standard.isBuyed = ballSkin.isBuyed
case 1:
billiard.isBuyed = ballSkin.isBuyed
case 2:
emoji.isBuyed = ballSkin.isBuyed
default:
standard.isBuyed = ballSkin.isBuyed
}
}
}
}
If I want to access the name of the skin in any other scene or view I call:
ballSkins.sharedInstance.billiard.name
But this is an optional every time! I don't know why or where the error is.
I suppose it is caused by
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
3rd line generates optional string because according to documentation
func decodeObject() -> Any?
and String(describing: ...) does not unwrap your value. You must unwrap all values from UserDefaults by yourself, providing defaultValue if nil is not possible

Why am I getting Cannot convert value of type Bool to expected argument type String

Getting several "Cannot convert value of type Bool to expected argument type String" errors. The method for encoding expects a string but it is getting a Bool?
Here is the code. See the attached image for errors.
import Foundation
class Restaurant {
var name = ""
var item = ""
var location = ""
var image = ""
var isVisited = false
var phone = ""
var rating = ""
init(name: String, item: String, location: String, phone: String, image: String, isVisited: Bool) {
self.name = name
self.item = item
self.location = location
self.phone = phone
self.image = image
self.isVisited = isVisited
}
class func makeNewsItem(_ notificationDictionary: [String: AnyObject]) -> Restaurant? {
if let name = notificationDictionary["name"] as? String,
let phone = notificationDictionary["phone"] as? String,
let location = notificationDictionary["location"] as? String {
let date = Date()
let image = ""
let visited = false
let item = ""
let newsItem = Restaurant(name: name, item: item, location: location, phone: phone, image: image, isVisited: visited)
NotificationCenter.default.post(name: Notification.Name(rawValue: RestaurantTableViewController.RefreshNewsFeedNotification), object: self)
return newsItem
}
return nil
}
}
extension Restaurant: NSCoding {
struct CodingKeys {
static var Name = "name"
static var Item = "item"
static var Location = "location"
static var Image = "image"
static var IsVisited:Bool = false
static var Phone = "phone"
static var Rating = "rating"
}
convenience init?(coder aDecoder: NSCoder) {
if let name = aDecoder.decodeObject(forKey: CodingKeys.Name) as? String,
let location = aDecoder.decodeObject(forKey: CodingKeys.Location) as? Date,
let phone = aDecoder.decodeObject(forKey: CodingKeys.Phone) as? String {
let date = Date()
let image = aDecoder.decodeObject(forKey: CodingKeys.Image) as? String
let visited:Bool = aDecoder.decodeBool(forKey: CodingKeys.IsVisited) as? String
let item = aDecoder.decodeObject(forKey: CodingKeys.Item) as? String
self.init(name: name, item: item, location: location, phone: phone, image: image, isVisited: visited)
} else {
return nil
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: CodingKeys.Name)
aCoder.encode(location, forKey: CodingKeys.Location)
aCoder.encode(phone, forKey: CodingKeys.Phone)
aCoder.encode(item, forKey: CodingKeys.Item)
aCoder.encode(image, forKey: CodingKeys.Image)
aCoder.encode(isVisited, forKey: CodingKeys.IsVisited)
aCoder.encode(rating, forKey: CodingKeys.Rating)
}
}
You can´t add a bool value to the forKey. This has to be a string value, so change it from:
aCoder.encode(isVisited, forKey: CodingKeys.IsVisited)
To:
aCoder.encode(isVisited, forKey: "IsVisited")
Same for:
let visited:Bool = aDecoder.decodeBool(forKey: CodingKeys.IsVisited) as? String
To:
let visited:Bool = aDecoder.decodeBool(forKey: "IsVisited") // note, no need for as? String here