Converting Swift Array to NSData for NSUserDefaults.StandardUserDefaults persistent storage - arrays

I'm trying to get my head around Swift (after being relatively competent with Obj-C) by making a small app. I would like to use NSUserDefaults to persistently save a small amount of data but I am having problems.
I initialise an empty array of tuples like this:
var costCategoryArray: [(name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float)]=[]
When the array has an entry, I want to save the array to NSUserDefaults with standard Swift code such as this:
NSUserDefaults.standardUserDefaults().setObject(costCategoryArray, forKey: "financialData")
NSUserDefaults.standardUserDefaults().synchronize()
I get an error saying that the tuple array doesn't conform to the AnyObject class. So I tried to turn it into NSData:
var myNSData: NSData = NSKeyedArchiver.archivedDataWithRootObject(costCategoryArray)
var myUnarchivedData: Array = NSKeyedUnarchiver.unarchiveObjectWithData(myNSData)
...but I get the same error during the conversion to NSData. The object being held by my array doesn't conform to AnyObject. I've also tried at each stage to make it immutable by using:
let immutableArray = costCategoryArray
Ive also tried creating a class instead of using tuples which I understood would make it comply with AnyObject:
class costCategory : NSObject {
var name : String
var defaultValue : Int
var thisMonthsEstimate : Int
var sumOfThisMonthsActuals : Int
var riskFactor : Float
var monthlyAverage : Float
init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
self.name = name
self.defaultValue = defaultValue
self.thisMonthsEstimate = thisMonthsEstimate
self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
self.riskFactor = riskFactor
self.monthlyAverage = monthlyAverage
}
}
But the new error is:
"Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')"
What is the problem with an array of tuples? Why can't I store an array of class objects? I feel like I need some expert advice as so far everything I try to do with Swift is pretty much incompatible...
Thanks!

Anything you are archiving to NSData and back needs to implement the NSCoding protocol. I found that in addition, my Swift class had to extend NSObject. Here is a quick example of a Swift class that encodes and decodes:
class B : NSObject, NSCoding {
var str : String = "test"
required init(coder aDecoder: NSCoder) {
str = aDecoder.decodeObjectForKey("str") as String
}
override init() {
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(str, forKey: "str")
}
}
// create an Object of Class B
var b : B = B()
// Archive it to NSData
var data : NSData = NSKeyedArchiver.archivedDataWithRootObject(b)
// Create a new object of Class B from the data
var b2 : B = NSKeyedUnarchiver.unarchiveObjectWithData(data) as B

value of "financialData" should be in quotes:
NSUserDefaults.standardUserDefaults().setObject("costCategoryArray", forKey: "financialData")
NSUserDefaults.standardUserDefaults().synchronize()

Related

Problem on RealmSwift: "Invalid array input: more values (1) than properties (0)." while trying to persist an Array of String

I'm trying to persist in a table view cell, the result of a quiz test with questions and I needed the array of answers given (String Array) so I decided to use RealmSwift.
I created this class and of course I created also a RealmString object in the same file to handle the possibility to persist arrays of String in Realm in this way:
class RealmString: Object {
dynamic var stringValue = ""
}
class Test: Object {
#objc dynamic var ID = UUID().uuidString
#objc dynamic var testScore : String = String()
#objc dynamic var testTitle : String = String()
#objc dynamic var testSubTitle : String = String()
#objc dynamic var dateOfExecution: String = String()
#objc dynamic var answersGiven: [String] {
get {
return _backingAnswersGiven.map { $0.stringValue }
}
set {
_backingAnswersGiven.removeAll()
_backingAnswersGiven.append(objectsIn: (newValue.map({ RealmString(value: [$0]) })))
}
}
let _backingAnswersGiven = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["answersGiven"]
}
override static func primaryKey() -> String? {
return "ID"
}
Now in the view controller:
I have a variable that stores the result (is an Int array that will take ten answers with values from 0 to 5, and these will later be converted to String)
i.e.: [0,2,2,3,4,5,2,1,0,2] -> ["0","2","2","3","4","5","2","1","0","2"]
and when an option is selected in a question the value is set with this function, everything works fine.
public var questionResults: [Int] = []
func setValueToQuestion(questionNumber: Int) {
questionResults[questionNumber] = optionChosen
}
When the test is completed successfully everything is saved in this way:
let test = Test()
test.ID = currentTest?.ID ?? UUID().uuidString
test.testTitle = testTitleLabel.text!
test.testScore = resultNumberLabel.text!
test.testSubTitle = resultLabel.text!
test.dateOfExecution = dateTimeString
test.answersGiven = questionResults.map({String($0)})
DBManager.sharedInstance.addData(object: test)
I tried the code separately also adding breakpoints and everything works in the flow, expect this line:
test.answersGiven = questionResults.map({String($0)})
that raises the error shown in the title: "Invalid array input: more values (1) than properties (0)."
I guess it can be an error of mapping maybe?
This value is then treated in the rest of flow as a simple swift array of String = [String]
There are a few issues which may be leading to that error.
First the RealmString property is not persisted because it needs #objc
dynamic var stringValue = ""
should be
#objc dynamic var stringValue = ""
Secondly, and this is important, Realm does not support primitives in Lists. Well, it kinda does but not very well.
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
See my answer to this question but in a nutshell, you need another class to store the string in - kind of like your RealmString class.
class StringClass: Object {
#objc dynamic var myString = ""
}
and then change the Test object property to use the StringClass property
#objc dynamic var answersGiven = [StringClass]()
and then I see you're trying to use a backed var and computed property but I am not sure why. It may be simpler to use use the var itself
let _backingAnswersGiven = List<RealmString>()
since the List collection already handles what's being computed.
For example, if you set the list you can set it to another list (which wipes out the current list). Or when you get the list let myStringList = test._backingAnswersGiven, gets all of the StringClasses in the list without having to .map over them.

Multiple Structs Arrays into a single struct type

I'm looking to take two struct arrays of array data, retrieved from multiple sources (web REST calls) and cast them into one singular struct array that I can use on a UITableView.
I originally went down the avenue of creating a multidimensional array, but proved difficult when I had to downcast the types as [AnyObject] and couldn't read into the properties.
Example Data:-
import UIKit
// First Set Of Data
struct Items : Codable{
// List of 'items'
let items : [Item]
}
struct Item: Codable{
// Items inside the array
let id : String
let name : String
let price : String
let imageURL : String
let weight : String
}
// ******** SECOND SET OF DATA
struct SecondSetOfItems : Codable {
let mainFopCollection : MainFopCollection?
}
struct MainFopCollection : Codable {
let sections : [Sections]?
}
struct Sections : Codable {
let id : String?
let fops : [Fops]?
}
struct Fops : Codable {
let sku : String?
let hash : Int?
let product : Product?
}
struct Product : Codable {
let sku : String?
let name : String?
let price : Price?
}
struct Price : Codable {
let current : Double?
let lpp : Bool?
let unit : Unit?
let type : String?
}
struct finalSetOfItems{
let name : String?
let price: String?
}
var array1 = [Items]()
var array2 = [SecondSetOfItems]()
// Originally tried:-
var multiArray = [[AnyObject]]()
multiArray.append(array1 as [AnyObject])
multiArray.append(array2 as [AnyObject])
// This part errors because I can't access this property
let name = multiArray[1][1].name
// Also tried a ternary operator (this part is within cellforrowat in my UITableViewController but I want the data to be dynamic, and not hard coded.
let name = indexPath.section == 0 ? array1![indexPath.row].name : array2![0].sections![0].fops![indexPath.row].product!.name
But then I couldn't access properties of the arrays as the names were too ambiguous, and as I'm using multiple data types it is hard to stipulate what exactly I'm looking for.
The second set of data is considerably more complex (and deeper) than the first set.
Desired Output:-
// Take complex array1 and array2 and change them into a type of finalSetOfItems
var array1 = [Items]()
var array2 = [SecondSetOfItems]()
var arrayOutput = [finalSetOfItems]()
Is there a way i can cast both sets of data into a singular struct type, so the properties can be accessed easier?
The end goal is to cast each array into its own section within a UITableView to denote where the data came from
Apologies if my question is badly worded, I'm still relatively new to Swift. Any help would be greatly appreciated
let firstItemSet = array1.flatMap{
$0.items
}.map{
FinalSetOfItems(name: $0.name, price: $0.price)
}
let secondItemSet = array2.flatMap{
$0.mainFopCollection?.sections ?? []
}.flatMap{
$0.fops ?? []
}.compactMap{
$0.product
}.map{
FinalSetOfItems(name: $0.name, price: $0.price)
}
let finalSet = firstItemSet + secondItemSet

Cannot convert value of type '[String?]' to expected argument type 'Video'

I have been trying to understand the problem below. Since I am new to Swift whatever I am coding may not make any sense at all and therefore there's no way to explain. I understand. The codes are copied from some online tutorials and I am trying to incorporate data persistence into it using Core Data. This is where I got stuck half way through.
Hope someone can help.
enter code here
Core Data model attached to the following view controller :
entity : Video
attribute : image - String
attribute : title - String
import UIKit
import CoreData
class VideoListScreenVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var videos : [Video] = [ ]
override func viewDidLoad() {
super.viewDidLoad()
videos = createArray()
}
func createArray() -> [Video] {
var tempVideos : [Video] = []
if let context = (UIApplication.shared.delegate as AppDelegate)?.persistentContainer.viewContext{
let videoX = Video(context: context)
let v1 = videoX.image
let t1 = videoX.title
let video1 = [v1,t1]
let video2 = [v1,t1]
let video3 = [v1,t1]
let video4 = [v1,t1]
let video5 = [v1,t1]
tempVideos.append(video1) // error : Cannot convert value of type '[String?]' to expected argument type 'Video'
// tempVideos.append(video2)
// tempVideos.append(video3)
// tempVideos.append(video4)
// tempVideos.append(video5)
return tempVideos
}
}
}
Let's consider the typing of each line:
This line:
var tempVideos : [Video]
means tempVideos is an array containing items of type Video
These lines:
let v1 = videoX.image
let t1 = videoX.title
means v1 is a String (I guess it's the URL of the image, which is a String), and t1 is also a String (the title of the video).
This line:
let video1 = [v1,t1]
means you are building an array with 2 items (v1 and t1).
v1 and t1 are both String items, so video1 is an array containing items of type String
Let's recap:
tempVideos is an array containing items of type Video
video1 is an array containing items of type String
So:
tempVideos.append(video1)
means I want to put an array of String (which is [String?]) inside an array that can only contain Video items.
Thus, the error:
error : Cannot convert value of type '[String?]' to expected argument type 'Video'
The solution is to create video1 as a Video object (I can't tell you how, that's your code, not mine), then you can do tempVideos.append(video1)

Create an array of Struct arrays?

I have a query that builds a Struct array from some objects. I want to append multiple of these structs into a single array so that i can use it populate a collectionview that is nested inside a collectionview. Im not sure this is the best idea but its all I've got a the moment.
So my Struct is:
struct CollectionStruct {
var name : String
var description : String
var title : String
var image : PFFile
var id: String
}
In my viewcontroller the variable to hold the struct is thus:
var packArray : [CollectionStruct] = []
var partArray : [CollectionStruct] = []
var packId = ""
So below what I'm doing is using an item of the previously created packArray to query the DB and build my partArray. Because i want to do this multiple times and append the partArray into a larger parent array i was thinking of using a for loop like:
var i = 0
for item in self.packArray as [AnyObject] {
self.packId = self.packArray[i].id
BuildArray.buildArrayFromQuery(queryForCollection: "Part", selectedPackID: self.packId, delegateSender: "DownloadPart", completeBlock: { (result) in
self.partArray = result
// append this to an array of partArray????
})
}
I don't even know where to start with this. Is it a bad idea?
essentially what i thought was to call this parent array in cellforitem maybe something like:
innerCell.imageCell.image = parentArray[indexPath.item].partArray[indexPath.item].image
but i could be way off.
----- EDIT -----
so i can use a multidimensional array inside the closure once self.partArray is set:
self.partArray = result
var parentArray = [[self.partArray]]
parentArray.append([self.partArray])
but this is unusable outside the closure. How to declare this multidimensional array before self.partArray is set?
------- EDIT -------
Looking into globals for this at the moment.
class GlobalPartArray {
private init() { }
static let sharedInstance = GlobalPartArray()
var globalPartArray = [CollectionStruct]()
}
class GlobalParentArray {
private init() { }
static let sharedInstance = GlobalParentArray()
var globalParentArray = [[GlobalPartArray]]()
}
then inside closure:
GlobalPartArray.sharedInstance.globalPartArray = result
//can not convert [CollectionStruct] to [GlobalPartArray]
GlobalParentArray.sharedInstance.globalParentArray.append(GlobalPartArray.sharedInstance.globalPartArray)
but it throws an error when trying to convert. I obviously need to convert this data somehow before appending to the GlobalParentArray.

Swift empty array does not have a member named .insert

I am new to Swift.
I am trying to get some data from a webservice and to loop the JSON data to make a simple array.
DataManager.getDataFromEndpoint{ (endpointData) -> Void in
let json = JSON(data: endpointData)
if let programsOnAir = json["data"]["data"]["on_air"].array{
var onAirArray = []
for onAir in programsOnAir {
var eventName = onAir["event_name"].string
var eventCover = onAir["event_cover"].string
var tuple = (name: eventName!, cover: eventCover!)
onAirArray.insert(tuple, atIndex: 1)
}
println(onAirArray)
}
}
I get an error where the member .insert does not exist
BUt if I init the array like this var onAirArray = [name: "something, cover: "somethingelse"] then it works.
I need to work with empty arrays and I need to be them mutable, because I have no idea what I may get from the JSON given by the API endpoint.
What am I doing wrong?
The problem is with this line:
var onAirArray = []
Since you haven't given the array an explicit type, this is creating a new instance of NSArray, which doesn't have a method called insert. Which is why this is probably the exact error message you're receiving.
'NSArray' does not have a member named 'insert'
To fix this, explicitly state the type of your array.
var onAirArray: [(String, String)] = []

Resources