Array of structs: UserDefaults, how to use? - swift

I've already check all of those topics:
How to save an array of custom struct to NSUserDefault with swift?
How to save struct to NSUserDefaults in Swift 2.0
STRUCT Array To UserDefaults
I have a struct containing some Strings and an other struct: MySection.
struct MySection {
var name: String = ""
var values: [MyRow] = []
}
And there is MyRow which is store in MySection.values
struct MyRow {
var value: String = ""
var quantity: String = ""
var quantityType: String = ""
var done: String = ""
}
Two arrays for use it
var arraySection: [MySection] = []
var arrayRow: [MyRow] = []
And in my application, I add dynamically some values in those arrays.
There is the delegate method for get datas from my second ViewController
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow())
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
And there is the manageSection function.
func manageSection(item: String) {
var i = 0
for _ in arraySection {
if arraySection[i].name == item {
arraySection.insert(MySection(), at: i + 1)
arraySection[i + 1].values = [arrayRow[arrayRow.count - 1]]
return
}
i += 1
}
arraySection.append(MySection())
arraySection[arraySection.count - 1].name = item
arraySection[arraySection.count - 1].values = [arrayRow[arrayRow.count - 1]]
}
My need is to store datas of the two arrays in UserDefaults (or CoreData maybe??) and use these datas when the user going back to the application.
I don't know how to do it, I've already try methods from the 3 topics but I'm not even doing a good job.
How can I do it?
Thanks guys!

Since both types contain only property list compliant types a suitable solution is to add code to convert each type to a property list compliant object and vice versa.
struct MySection {
var name: String
var values = [MyRow]()
init(name : String, values : [MyRow] = []) {
self.name = name
self.values = values
}
init(propertyList: [String: Any]) {
self.name = propertyList["name"] as! String
self.values = (propertyList["values"] as! [[String:String]]).map{ MyRow(propertyList: $0) }
}
var propertyListRepresentation : [String: Any] {
return ["name" : name, "values" : values.map { $0.propertyListRepresentation }]
}
}
struct MyRow {
var value: String
var quantity: String
var quantityType: String
var done: String
init(value : String, quantity: String, quantityType: String, done: String) {
self.value = value
self.quantity = quantity
self.quantityType = quantityType
self.done = done
}
init(propertyList: [String:String]) {
self.value = propertyList["value"]!
self.quantity = propertyList["quantity"]!
self.quantityType = propertyList["quantityType"]!
self.done = propertyList["done"]!
}
var propertyListRepresentation : [String: Any] {
return ["value" : value, "quantity" : quantity, "quantityType" : quantityType, "done" : done ]
}
}
After creating a few objects
let row1 = MyRow(value: "Foo", quantity: "10", quantityType: "Foo", done: "Yes")
let row2 = MyRow(value: "Bar", quantity: "10", quantityType: "Bar", done: "No")
let section = MySection(name: "Baz", values: [row1, row2])
call propertyListRepresentation to get a dictionary ([String:Any]) which can be saved to User Defaults.
let propertyList = section.propertyListRepresentation
Recreation of the section is quite easy, too
let newSection = MySection(propertyList: propertyList)
Edit
Use the propertyList initializer only if you get data from UserDefaults in all other cases use the other initializer.
For example replace
#IBAction func addButtonPressed(_ sender: Any) {
newProducts.append(MyRow(propertyList: ["":""]))
newProducts[newProducts.count - 1].value = nameTextField.text!
newProducts[newProducts.count - 1].quantity = quantityTextField.text!
newProducts[newProducts.count - 1].quantityType = type
newProducts[newProducts.count - 1].done = "No"
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
with
#IBAction func addButtonPressed(_ sender: Any) {
let row = MyRow(value: nameTextField.text!,
quantity: quantityTextField.text!,
quantityType: type,
done: "No")
newProducts.append(row)
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
and replace
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow(propertyList: ["":""]))
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
with
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(newItem[0])
manageSection(item: sectionPick)
listTableView.reloadData()
}
Basically first create the object, then append it to the array. The other way round is very cumbersome.

Related

Can we use for loop for items of a struct?

I have a struct like this:
struct TestType {
var value1: String? = "1"
var value2: String? = "2"
var value3: String? = "3"
var value4: String? = "4"
var value5: String? = "5"
var value6: String? = "6"
var value7: String? = "7"
var value8: String? = "8"
var value9: String? = "9"
}
I want be able to use a for loop on values of TestType, like this code in below, is this possible in swift?
Or even any kind of loop support for items of a struct?
func myTestFunction() {
let test: TestType = TestType()
test(value1...value9).forEach { value in
if let unwrappedValue: String = value {
print(unwrappedValue)
}
}
}
You can use Mirror to achieve that, something like this:
struct TestType {
var value1: String? = "1"
var value2: String? = "2"
var value3: String? = "3"
var value4: String? = "4"
var value5: String? = "5"
var value6: String? = "6"
var value7: String? = "7"
var value8: String? = "8"
var value9: String? = "9"
func iterateThroughProperties() {
for property in Mirror(reflecting: self).children where property.label != nil {
print("name: \(property.label!)")
print("value: \(property.value)")
print("type: \(type(of: property.value))")
}
}
}
[String?](mirrorChildValuesOf: TestType())
public extension Array {
/// Create an `Array` if `subject's` values are all of one type.
/// - Note: Useful for converting tuples to `Array`s.
init?<Subject>(mirrorChildValuesOf subject: Subject) {
guard let array =
Mirror(reflecting: subject).children.map(\.value)
as? Self
else { return nil }
self = array
}
}

Inner filtering of array doesn't filter swift

I am trying to filter an array of structs that has array. Below are the data structures I am using. I want the inner array filtered also but it doesn't work
var objects = [SomeObject]() //array of objects
var filteredObject = [SomeObject]() //filtered array
var isSearching = false
struct SomeObject {
var sectionName: String
var sectionObjects : [History]
}
struct History {
var firstName: String
var lastName: Int
}
func searchBar(_ text: String) {
filteredObject = objects.filter({ (obj: SomeObject) -> Bool in
return obj.sectionObjects.filter { $0.firstName.contains(text.lowercased())}.isEmpty
})
print("====", filteredObject, "fill===")
}
let history = History(firstName: "John", lastName: 1)
let anotherHistroy = History(firstName: "Dee", lastName: 2)
let historyArray = [history, anotherHistroy]
let newObject = SomeObject(sectionName: "Section 1", sectionObjects: historyArray)
objects.append(newObject)
searchBar("Jo") // printing of filtered object should not have another history in it's sectionObjects
You might be looking for something like this:
func searchBar(_ text: String) {
filteredObject = []
for var ob in objects {
ob.sectionObjects = ob.sectionObjects.filter {
$0.firstName.contains(text)
}
if !ob.sectionObjects.isEmpty {
filteredObject.append(ob)
}
}
print("====", filteredObject, "fill===")
}
Could perhaps be done more elegantly with reduce(into:), but on the whole it is best to start simply by saying exactly what you mean. You can tweak as desired to take account of case sensitivity.

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want

Swift 3 which is the best way to store of an Object with array etc

I started to develop with Swift 3 and i´m getting crazy. Following Situation:
class subObject
{
var name : String
var list : [Int]
init( _name : String, _list : [Int] ){
self.name = _name
self.list = _list
}
}
class mainObject
{
var subObjectList : [subObject]
init( _list : [subObject] ){
self.subObjectList = _list
}
}
var data : [mainObject]
Which way is state of the art to store var data : [mainObject] persistently. I've already unsuccessfully tried .plistand NSKeyedArchiver.
Sorry but my english is worse.
NSCoding cannot be used because the classes aren't subclasses of NSObject.
Since all properties in both classes are property list compliant you could add a computed property propertyListRepresentation and an appropriate initializer.
Class names are supposed to start with a capital letter and parameters starting with an underscore are unusual in Swift.
class SubObject
{
var name : String
var list : [Int]
init(name : String, list : [Int] ){
self.name = name
self.list = list
}
init?(dictionary : [String:Any]) {
guard let name = dictionary["name"] as? String,
let list = dictionary["list"] as? [Int] else { return nil }
self.name = name
self.list = list
}
var propertyListRepresentation : [String:Any] {
return ["name" : name, "list" : list]
}
}
class MainObject
{
var subObjectList : [SubObject]
init(list : [SubObject] ){
self.subObjectList = list
}
init(propertyList : [[String:Any]] ){
self.subObjectList = propertyList.flatMap{ SubObject(dictionary: $0) }
}
var propertyListRepresentation : [[String:Any]] {
return subObjectList.map{ $0.propertyListRepresentation }
}
}
To use it:
let subs = [SubObject(name: "Foo", list: [1, 2, 3]), SubObject(name: "Bar", list: [4, 5, 6])]
let main = MainObject(list: subs)
let list = main.propertyListRepresentation
let data = try! PropertyListSerialization.data(fromPropertyList: list, format: .xml, options: 0)
print(String(data:data, encoding: .utf8)!)
let restoredList = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]]
let restoredMain = MainObject(propertyList: restoredList)

How call method to filter by year

I'm making easy example.
I'm trying to filter movies by year using func: filterByYear.
I have an error in one line on the bottom:
'var filterYear = MovieArchive.filterByYear(1980)' Compiler info is: 'Type 'MovieArchive' has no member filterByYear’
import UIKit
class Movie {
let title: String
let director: String
let releaseYear: Int
init(title: String, director: String, releaseYear: Int){
self.title = title
self.director = director
self.releaseYear = releaseYear
}
}
class MovieArchive{
var movies : [Movie]
init(movies:[Movie]){
self.movies = movies
func filterByYear(year:Int) -> [Movie]{
var filteredArray = [Movie]()
for movie in movies{
if movie.releaseYear == year {
filteredArray.append(movie)
}
}
return filteredArray
}
}
}
var newMovie1 = Movie(title: "IT", director: "S.S", releaseYear: 1980)
var newMovie2 = Movie(title: "PP", director: "O.N", releaseYear: 2003)
var moviesArray = [newMovie1, newMovie2]
var myArchive = MovieArchive(movies: moviesArray)
var filterYear = MovieArchive.filterByYear(1980)
var firstMovie = filterYear[0]
print(firstMovie)
You are calling the method on the class itself, not an instance of it. You would have to create an instance of the class like this:
let myMovieArchive = MovieArchive(movies: [Some Array])
and then call the method on the instance.
print(myMovieArchive.filterByYear)
So in your case, call it on myArchive, not MovieArchive.
Even though the question has been answered, you can simplify your filterByYear method to make it a bit more Swifty:
func filterByYear(year:Int) -> [Movie]
{
return movies.filter({ $0.releaseYear == year })
}