converting custom object to NSData Swift - ios

I have been researching all over the interweb, especially Stack overflow, and my own references I have at home, but I have not been able to figure out what is wrong with my code. I have spent most of my day trying to figure this issue out, and I hope somebody here can help point me in the correct direction.
Setup:
I have a chess app that I am building in Swift 3.0, and the structure is as follows: BoardModel is the class that holds all of the data about the game, Piece is a class within board model which holds data about itself within the BoardModel, Piece also has a PieceType enum for .knight, .king, etc.
The BoardModel has a 2D array of Piece representing the chess board. Now, with each move, I want to save the game data in Game Center, but before I can even try to store the game data, the encoding throws an error and that is where I am. The error just points at AppDelegate with the statement: "Thread: 1 signal SIGABRT".
Here is the code that is the problem along with the Piece class:
let pieceData = NSKeyedArchiver.archivedData(withRootObject: board.board[0][0]) // where the error is thrown
class Piece: NSObject, NSCoding {
var isSelected: Bool
var type: PieceType
var isWhite: Bool
var isFirstMove: Bool
var symbol: String!
var position: (row: Int, col: Int)!
override init() {
isSelected = false
type = PieceType.empty
isWhite = true
isFirstMove = true
}
required init(coder aDecoder: NSCoder) {
isSelected = aDecoder.decodeBool(forKey: "isSelected")
type = aDecoder.decodeObject(forKey: "type") as! BoardModel.PieceType
isWhite = aDecoder.decodeBool(forKey: "isWhite")
isFirstMove = aDecoder.decodeBool(forKey: "isFirstMove")
symbol = aDecoder.decodeObject(forKey: "symbol") as! String
position = aDecoder.decodeObject(forKey: "position") as! (Int, Int)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(isSelected, forKey: "isSelected")
aCoder.encode(type, forKey: "type")
aCoder.encode(isWhite, forKey: "isWhite")
aCoder.encode(isFirstMove, forKey: "isFirstMove")
aCoder.encode(symbol, forKey: "symbol")
aCoder.encode(position, forKey: "position")
}
init(isSelected: Bool, type: PieceType, isWhite: Bool, isFirstMove: Bool, symbol: String, position: (Int, Int)) {
self.isSelected = isSelected
self.type = type
self.isWhite = isWhite
self.isFirstMove = isFirstMove
self.symbol = symbol
self.position = position
}
func setPosition(to newPosition: (row: Int, col: Int)) {
position = newPosition
}
}

if your Enum is like this PieceType and type is Int
enum PieceType : Int {
case empty
case notEmpty
}
Then write encode and decode method like this way
required init(coder aDecoder: NSCoder) {
isSelected = aDecoder.decodeBool(forKey: "isSelected")
type = PieceType(rawValue: aDecoder.decodeObject(forKey: "type") as! Int)!
isWhite = aDecoder.decodeBool(forKey: "isWhite")
isFirstMove = aDecoder.decodeBool(forKey: "isFirstMove")
symbol = aDecoder.decodeObject(forKey: "symbol") as! String
position = aDecoder.decodeObject(forKey: "position") as! (Int, Int)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(isSelected, forKey: "isSelected")
aCoder.encode(type.rawValue, forKey: "type")
aCoder.encode(isWhite, forKey: "isWhite")
aCoder.encode(isFirstMove, forKey: "isFirstMove")
aCoder.encode(symbol, forKey: "symbol")
aCoder.encode(position.row, forKey: "position.row")
aCoder.encode(position.col, forKey: "position.col")
}
i have check bellow code and it work's
let pice = Piece(isSelected: true, type: .empty, isWhite: true, isFirstMove: true, symbol: "Test", position: (2, 10))
let pieceData = NSKeyedArchiver.archivedData(withRootObject:pice)
print(pieceData)
And Out put is
386 bytes

Related

Feature layer markers not getting displayed in Esri ios SDK

I'm using ESRI iOS SDK 10.2.5,my app was built using swift 2.3 and i have converted it to 3.2 now,everythng works fine.
but there's a little problem in displaying the markers in feature layer which used to work correctly in swift 2.3
the method signature have slightly changed after migration .
here's the method signature for 2.3 (works fine):
func featureLayer(featureLayer: AGSFeatureLayer!, operation op: NSOperation!, didQueryFeaturesWithFeatureSet featureSet: AGSFeatureSet!) {
if (featureLayer == self.featureLayer) {
// featureset has more than 1 element
for poi in featureSet.features {
var attr = [String: AnyObject]()
attr["NAME"] = poi.allAttributes()[LanguageController.localizedString("poi_detail_name")] as? String;
attr["DETAIL"] = poi.allAttributes()["PHONE_NUMBER"] as? String;
attr["CATEGORY_EN"] = poi.allAttributes()["FACILITY_CAT_EN"] as? String;
attr["IS_OFFICE"] = false;
attr["IS_HDKP"] = false
let graphic = AGSGraphic(
geometry: poi.geometry,
symbol: getSymbolForGraphic(poi as! AGSGraphic),
attributes: attr)
self.mapLayer.addGraphic(graphic);
}
SVProgressHUD.dismiss()
}
method signature for swift 3.2 :
func featureLayer(_ featureLayer: AGSFeatureLayer!, operation op: Operation!, didQueryFeaturesWith featureSet: AGSFeatureSet!) {
if (featureLayer == self.featureLayer) {
// problem here,featureset.feature has 0 elements
for poi in featureSet.features {
var attr = [String: Any]()
attr["NAME"] = (poi as AnyObject).allAttributes()[LanguageController.localizedString("poi_detail_name")] as? String;
attr["DETAIL"] = (poi as AnyObject).allAttributes()["PHONE_NUMBER"] as? String;
attr["CATEGORY_EN"] = (poi as AnyObject).allAttributes()["FACILITY_CAT_EN"] as? String;
attr["IS_OFFICE"] = false as Any
attr["IS_HDKP"] = false as Any
let graphic = AGSGraphic(
geometry: (poi as AnyObject).geometry,
symbol: getSymbolForGraphic(poi as! AGSGraphic),
attributes: attr)
self.mapLayer.addGraphic(graphic);
}
SVProgressHUD.dismiss()
}
Model class for manipulating features :
class POICategory: NSObject, NSCoding
{
var isActive : Bool;
var name : String;
var nameFilter : String;
var key : String;
init(isActive: Bool,name : String, nameFilter: String, key: String){
self.isActive = isActive;
self.name = name;
self.nameFilter = nameFilter;
self.key = key;
}
required init?(coder aDecoder: NSCoder) {
self.isActive = aDecoder.decodeObject(forKey: "isActive") as? Bool
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.nameFilter = aDecoder.decodeObject(forKey: "nameFilter") as! String
self.key = aDecoder.decodeObject(forKey: "key") as! String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.isActive, forKey: "isActive")
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.nameFilter, forKey: "nameFilter")
aCoder.encode(self.key, forKey: "key")
}
}
i have debugged the code and came to know that the featureSet.feature has no elements in swift 3.2 but has elements in 2.3,that is why it is not entering the loop and setting the graphics to the marker
why this happens,since this method is from esri's delegate..is this bug from their end ?
please help me in fixing this issue,if anyone has any idea on this
there's nothing wrong in migration from 2.3 to 3.2,only thing which might break according to me is the code in your model class
try changing your model class in the second init from
self.isActive = aDecoder.decodeObject(forKey: "isActive") as? Bool
to
self.isActive = aDecoder.decodeBool(forKey: "isActive")
you are trying to read a boolean value like object, which might be the issue

Issues with userdefaults usage swift 3

I just started learning Swift so the level of this question may be obvious enough for you, however, I simply can't understand what to do.
I "created" a simple app which allows you to add logs of your day. Each cell stores the time you added, a custom icon that changes depending on the time, and, the log text itself (simple text).
Everything works fine. But, as I didn't know about the "Userdefaults" stuff, the clock resets every time I kill the app.
I read many articles about Userdefaults but I have no idea what to do to keep saving my data even when I kill the app.
Here's what I tried to do:
class ToDoItem: NSObject, NSCoding {
var title: String
var date: String
var type: String!
public init(title: String, date: String, type: String) {
self.title = title
self.date = date
self.type = type
}
required init?(coder aDecoder: NSCoder)
{
// Try to unserialize the "title" variable
if let title = aDecoder.decodeObject(forKey: "title") as? String
{
self.title = title
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
self.date = dateFormatter.string(from: Date())
let hour = NSCalendar.current.component(.hour, from: Date())
var tempType = ""
switch hour {
case 5..<9: tempType = "morning_1"
case 6..<12: tempType = "morning_2"
case 12: tempType = "noon_1"
case 13..<16: tempType = "afternoon_1"
case 16..<20: tempType = "dusk_1"
case 20..<23: tempType = "evening_1"
case 23..<00: tempType = "midnight_1"
default: tempType = "morning_1"
}
self.type = tempType
}
else
{
// There were no objects encoded with the key "title",
// so that's an error.
return nil
}
let userDefaults = UserDefaults.standard
userDefaults.set(true, forKey: "title")
}
func encode(with aCoder: NSCoder)
{
// Store the objects into the coder object
aCoder.encode(self.title, forKey: "title")
let defaults = UserDefaults.standard
defaults.set(false, forKey: "title")
}
}
extension Collection where Iterator.Element == ToDoItem
{
// Builds the persistence URL. This is a location inside
// the "Application Support" directory for the App.
private static func persistencePath() -> URL?
{
let url = try? FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
return url?.appendingPathComponent("todoitems.bin")
}
// Write the array to persistence
func writeToPersistence() throws
{
if let url = Self.persistencePath(), let array = self as? NSArray
{
let data = NSKeyedArchiver.archivedData(withRootObject: array)
try data.write(to: url)
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 10, userInfo: nil)
}
}
// Read the array from persistence
static func readFromPersistence() throws -> [ToDoItem]
{
if let url = persistencePath(), let data = (try Data(contentsOf: url) as Data?)
{
if let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [ToDoItem]
{
return array
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 11, userInfo: nil)
}
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 12, userInfo: nil)
}
}
}
can anyone help me or at least point what I have to do? thank you!
You're using NSKeyedArchiver and NSKeyedUnarchiver which is a different mechanism than UserDefaults. Both are perfectly valid, but you have to pick one, they don't work together.
Here you are archiving and unarchiving an array of ToDoItem. For this to work, ToDoItem needs to be archivable, meaning it must implement the NSCoding protocol, which is:
public protocol NSCoding {
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
All the properties that you want to save must be added to/extracted from the NSCoder object. Here is an example:
class ToDoItem: NSObject, NSCoding
{
var title: String
var date: Date
required init?(coder aDecoder: NSCoder)
{
guard let title = aDecoder.decodeObject(forKey: "title") as? String else {
return nil
}
guard let date = aDecoder.decodeObject(forKey: "date") as? Date else {
return nil
}
self.title = title
self.date = date
}
func encode(with aCoder: NSCoder)
{
aCoder.encode(self.title, forKey: "title")
aCoder.encode(self.date, forKey: "date")
}
}

swift Cannot convert value of type '[Liquor]' to expected argument type 'inout _'

I go through some similar questions but still cannot figure out why this happen.
var liquors = [Liquor]()
func loadSampleLiquors(){
let photo1 = UIImage(named: "Chateau Lafite Rothschild 1993")
let liquor1 = Liquor(name: "Chateau Lafite Rothschild", year: "1993", photo: photo1, rating: 7)
liquors += [liquor1] // Here is the error happen
}
the error messageis : Cannot convert value of type '[Liquor]' to expected argument type 'inout _'
This is probably because the "year" may be nil, but I go through my code that it should work well, I try to fix it using "if let liquors = xxx", but then there will be a EXC_BAD_INSTRUCTION on the decode function, So I post all my code under here:
here is my liquor class:
var name: String
var year: String
var photo: UIImage?
var rating: Int
struct PropertyKey {
static let nameKey = "name"
static let yearKey = "year"
static let photoKey = "photo"
static let ratingKey = "rating"
}
I use NSCoding to store data:
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.nameKey)
aCoder.encode(year, forKey: PropertyKey.yearKey)
aCoder.encode(photo, forKey: PropertyKey.photoKey)
aCoder.encode(rating, forKey: PropertyKey.ratingKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let year = aDecoder.decodeObject(forKey: PropertyKey.yearKey) as! String
let photo = aDecoder.decodeObject(forKey: PropertyKey.photoKey) as? UIImage
let rating = aDecoder.decodeInteger(forKey: PropertyKey.ratingKey)
self.init(name: name, year:year, photo: photo, rating: rating)
}
You're missing the unwrap operator. Try this:
let liquor1 = Liquor(name: "Chateau Lafite Rothschild", year: "1993", photo: photo1, rating: 7)!
notice the "!" at the end
The reason you need to unwrap is because Liquor init can return nil, so it returns an optional. Thats the ? at the end of init
required convenience init?

Strange issue in saving class in NSUserDefaults

I have the next class:
class UnreadMessages: NSObject {
let publicID: String
let messagesCount: Int
init(publicID: String, messagesCount: Int) {
self.publicID = publicID
self.messagesCount = messagesCount
}
required init(coder aDecoder: NSCoder) {
self.publicID = aDecoder.decodeObjectForKey("publicID") as! String
self.messagesCount = aDecoder.decodeObjectForKey("messagesCount") as! Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(publicID, forKey: "publicID")
aCoder.encodeObject(messagesCount, forKey: "messagesCount")
}
}
and I'm saving it in NSUserDefault as:
var chatList = [UnreadMessages]()
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(chatList), forKey: "ChatList")
and then I try to get these values as:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("ChatList") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
print(blog)
}
but when I try to print my blog.publicID it gives me the next error:
error: :2:10: error: 'UnreadMessages' is ambiguous for type lookup in this context blog as! UnreadMessages
How can I fix that?

Converting to Swift 3 broke custom class encoding/decoding

So I've just converted a small app from Swift 2.2 to Swift 3. I've gotten rid of the usual errors and bits of mop up required after the auto converter but I've got a run time issue that I can't work out.
I've got a custom class that I am saving to NSUserDefaults with the NSCoding protocol. When I try to decode the encoded object from NSUserDefaults it fails on the guard let duration = decoder.decodeObject(forKey: "duration") as? Int line as duration is printing as nil. Decoding the title string works fine however so at least that line of the encode function is working correctly.
This worked fine in 2.2 and I can't find anything indicating that Swift 3 made changes to the NSCoding. Any help would be much appreciated.
class TimerModel: NSObject, NSCoding, AVAudioPlayerDelegate {
var name: String
var active: Bool
var paused: Bool
var duration: Int
var remainingWhenPaused: Int?
var timerEndTime: Date?
var timerStartTime: Date?
var audioAlert: AlertNoise
var UUID: String
var colorScheme: BaseColor
var alarmRepetitions: Int
var timerRepetitions: Int
var currentTimerRepetition: Int
var audioPlaying: Bool
var player: AVAudioPlayer = AVAudioPlayer()
var countDownTimer: Timer = Timer()
var delegate: timerProtocol? = nil
init(withName name: String, duration: Int, UUID: String, color: BaseColor, alertNoise: AlertNoise, timerRepetitions: Int, alarmRepetitions: Int) {
self.name = name
self.active = false
self.paused = false
self.duration = duration
self.UUID = UUID
self.audioAlert = alertNoise
self.colorScheme = color
self.alarmRepetitions = alarmRepetitions
self.audioPlaying = false
self.timerRepetitions = timerRepetitions
self.currentTimerRepetition = 0
super.init()
}
convenience override init() {
self.init(withName: "Tap Timer 1", duration: 10, UUID: Foundation.UUID().uuidString, color: .Red, alertNoise: .ChurchBell, timerRepetitions: 1, alarmRepetitions: 0)
}
// MARK: NSCoding
required convenience init? (coder decoder: NSCoder) {
print("in init coder:")
print("Name: \(decoder.decodeObject(forKey: "name"))")
print("Duration: \(decoder.decodeObject(forKey: "duration"))")
guard let name = decoder.decodeObject(forKey: "name") as? String
else {
print("init coder name guard failed")
return nil
}
guard let duration = decoder.decodeObject(forKey: "duration") as? Int
else {
print("init coder duration guard failed")
print("duration: \(decoder.decodeObject(forKey: "duration"))")
return nil
}
guard let audioAlertRawValue = decoder.decodeObject(forKey: "audioAlert") as? String
else {
print("init coder audioAlert guard failed")
return nil
}
guard let UUID = decoder.decodeObject(forKey: "UUID") as? String
else {
print("init coder UUID guard failed")
return nil
}
guard let colorSchemeRawValue = decoder.decodeObject(forKey: "colorScheme") as? String
else {
print("init coder colorScheme guard failed")
return nil
}
guard let alarmRepetitions = decoder.decodeObject(forKey: "alarmRepetitions") as? Int
else {
print("init coder alarmRepetitions guard failed")
return nil
}
guard let timerRepetitions = decoder.decodeObject(forKey: "timerRepetitions") as? Int
else {
print("init coder timerRepetitions guard failed")
return nil
}
guard let audioAlert = AlertNoise(rawValue: audioAlertRawValue)
else{
print("No AlertNoise rawValue case found")
return nil
}
guard let colorScheme = BaseColor(rawValue: colorSchemeRawValue)
else{
print("No BaseColor rawValue case found")
return nil
}
print("initCoder guards passed, initing timer")
print("\(name), \(duration), \(UUID), \(colorScheme), \(audioAlert), \(timerRepetitions), \(alarmRepetitions)")
self.init(withName: name, duration: duration, UUID: UUID, color: colorScheme, alertNoise: audioAlert, timerRepetitions: timerRepetitions, alarmRepetitions: alarmRepetitions)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.duration, forKey: "duration")
aCoder.encode(self.audioAlert.rawValue, forKey: "audioAlert")
aCoder.encode(self.UUID, forKey: "UUID")
aCoder.encode(self.colorScheme.rawValue, forKey: "colorScheme")
aCoder.encode(self.alarmRepetitions, forKey: "alarmRepetitions")
aCoder.encode(self.timerRepetitions, forKey: "timerRepetitions")
}
So it seems the solution is simple if a little unintuitive.
So I encoded the class ivars with the general method encode(self.ivar, forKey: "keyName") however if that ivar is an int it needs to be decoded with decodeInteger(forKey: "keyName") - this entails getting rid of the guard statements as well since this method return an non-optional. Seems odd to have to decode with the integer specific method if it was decoded with the generalist method - this wasn't the case in Swift 2.2.
Excellent answer by SimonBarker and it solved the same issue I had. I eventually applied his solution to my own code and revised it too so that encoding is done with the generalist method. You can "force" encoding with the generalist method by using:
func encode(_ objv: Any?, forKey key: String)
So in your code you could use:
aCoder.encode(self.name as Any?, forKey: "name")
That way self.name is encoded as an object and does not break your existing code, namely: decoder.decodeObject(forKey: "name") as? String
This might not be the most elegant solution, but at least it worked for me without the need to change code that worked beautifully in Swift 2.3, but which was broken in Swift 3...

Resources