Can we use for loop for items of a struct? - swift

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
}
}

Related

How to create a JSON in SWFT using users input data?

I need to create a JSON like this:
{
"var1": "1",
"var2": "2",
"newArray": [{
"Deleted": false
}]
}
My Codable looks like this:
struct RequestModel: Codable {
var var1: String?
var var1: String?
var Treatments: [Treat]
}
struct Treat: Codable {
var adminBy: String?
}
So, now I need to create the above JSON... I tried the following:
let adminBy = adminByTextField.text ?? ""
let VAR1 = var1TextField.text ?? ""
let VAR2 = var2TextField.text ?? ""
let dict = ["adminBy":adminBy] as! NSDictionary
///And I TRY to create my JSON here
private func createRequestObject() -> RequestModel {
let request = RequestModel(var1: VAR1,
var2: VAR2,
Treatments: dict)
return request
}
But I get this error:
I don't understand why this error occurs or how to fix it.
any advice would be appreciated.
You should not be using an NSDictionary here. You RequestModel object requires an array of Treat.
You could create the array in the following way and pass it to your RequestModel :
let adminBy = adminByTextField.text ?? ""
let VAR1 = var1TextField.text ?? ""
let VAR2 = var2TextField.text ?? ""
let treats = [Treat(adminBy: adminBy)]
let request = RequestModel(var1: VAR1, var2: VAR2, Treatments: treats)

How to get all property names in nested Structs

Lets assume I have the following struct:
struct Location: Codable, Loopable {
var altitude: Double?
var coordinate: Coordinate?
struct Coordinate: Codable, Loopable {
var latitude: Double?
var longitude: Double?
}
var course: Double?
var courseAccuracy: Double?
var floor: Floor?
struct Floor: Codable, Loopable {
var level: Int?
}
var horizontalAccuracy: Double?
var speed: Double?
var speedAccuracy: Double?
var timestamp: Timestamp?
struct Timestamp: Codable, Loopable {
var year: Int?
var month: Int?
var day: Int?
var hour: Int?
var minute: Int?
var second: Int?
}
var verticalAccuracy: Double?
var deviceName: String?
var validPosition: Bool?
}
Now I want to implement two methods for structs. One should return all property names with all parents in its name and the other one should return all values of these properties.
The result should look like this for the first method (lets name it allProperties()):
["altitude", "coordinate.latitude", "coordinate.longitude", "course", "courseAccuracy", "floor.level", "horzontalAccuracy", "speed", "speedAccuracy", "timeststamp.year", "timestamp.month", "timestamp.day", "timeststamp.hour", "timestamp.minute", "timestamp.second", "verticalAccuracy", "deviceName", "validPosition"]
The result of the second method (lets name it allValues()) should look like this:
[500.0, 48.000000, 10.00000, 120.0, 5.0, 4, 5.0, 3.0, 1.0, 2021, 07, 25, 22, 43, 50, 10.0, "iPhone", true]
As you can see my example struct is nested with other structs. In my real project, the structs have more than two nested "levels". Also the property types are not unique. On the very bottom level they are all basic data types (Double, Bool, String, Int).
I tried to modify the solution for parsing nested structs with an recursive approach from this thread, which seems to be an elegant solution: How to loop over struct properties in Swift?
Now my problem:
The method for the property names only gives an array with "some" back.
But the method for the property values returns the correct Array
["some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some"]
This was my code for the protocol so far:
protocol Loopable {
func allProperties(limit: Int) -> [String]
func allValues(limit: Int) -> [Any]
}
extension Loopable {
func allProperties(limit: Int = Int.max) -> [String] {
return props(obj: self, count: 0, limit: limit)
}
func allValues(limit: Int = Int.max) -> [Any] {
return values(obj: self, count: 0, limit: limit)
}
private func props(obj: Any, count: Int, limit: Int) -> [String] {
let mirror = Mirror(reflecting: obj)
var result: [String] = []
for (prop, val) in mirror.children {
guard let prop = prop else { continue }
if limit == count {
result.append(prop)
} else {
let subResult = props(obj: val, count: count + 1, limit: limit)
subResult.count == 0 ? result.append(prop) : result.append(contentsOf: subResult)
}
}
return result
}
private func values(obj: Any, count: Int, limit: Int) -> [Any] {
let mirror = Mirror(reflecting: obj)
var result: [Any] = []
for (_, val) in mirror.children {
//guard let val = val else { continue } // This line does not compile
if limit == count {
result.append(val)
} else {
let subResult = values(obj: val, count: count + 1, limit: limit)
subResult.count == 0 ? result.append(val) : result.append(contentsOf: subResult)
}
}
return result
}
}
What am I doing wrong here? Why are the property labels always "some"?
So the issue with the “some” stems from your values being optional, optional values are an enumeration so your reflection is picking that up. This is why you cannot perform the guard in private func values(obj: Any, count: Int, limit: Int) -> [Any] as you are not unwrapping to a concrete type.
As all your subtypes confirm to Loopable we can refactor your code to check if the type conforms to Loopable. It is also more efficient to check if something is empty than if its count is equal to zero, so you should use that property where you can.
We’re now using the prop value as a prefix so that we can get the name that you are looking for, however, because your values are optional they are wrapped in an enum so you have to strip the .some from the string. The type of val is Any, this means we cannot unwrap it if it is an optional without knowing what its concrete type is, hence we need to do the above dance to remove the .some from the prefix.
import Foundation
protocol Loopable {
func allProperties() -> [String]
}
extension Loopable {
func allProperties() -> [String] {
return props(obj: self)
}
private func props(obj: Any, prefix: String = "") -> [String] {
let mirror = Mirror(reflecting: obj)
var result: [String] = []
for (prop, val) in mirror.children {
guard var prop = prop else { continue }
// handle the prefix
if !prefix.isEmpty {
prop = prefix + prop
prop = prop.replacingOccurrences(of: ".some", with: "")
}
if let _ = val as? Loopable {
let subResult = props(obj: val, prefix: "\(prop).")
subResult.isEmpty ? result.append(prop) : result.append(contentsOf: subResult)
} else {
result.append(prop)
}
}
return result
}
}
Here is a simple struct that we can test the above code with.
struct User: Loopable {
let name: String
let age: Age?
struct Age: Loopable {
let value: Int?
let day: Day?
struct Day: Loopable {
let weekday: Int?
}
}
}
let user = User(name: "mike", age: .init(value: 20, day: .init(weekday: 5)))
print(user.allProperties())
This will print out the following
["name", "age.value", "age.day.weekday"]

How to convert Array of Struct to List Realm?

i want to convert Array from struct to List Realm .
static func mapGenreResponsetoGenreEntity( input genre: [GenreModel]) -> List {
var list = List<GenreEntity>()
return genre.map { result in
let newGenre = GenreEntity()
newGenre.gamesCount = result.gamesCount ?? 0
newGenre.id = result.id ?? 0
newGenre.imageBackground = result.imageBackground ?? "Unknown"
newGenre.name = result.name ?? "Unknown"
newGenre.slug = result.slug ?? "Unknown"
list.append(newGenre)
return list
}
}
and the genre is
struct GenreModel: Codable {
let gamesCount : Int?
let id : Int?
let imageBackground : String?
let name : String?
let slug : String?
}
How can i convert from array genre (Struct) to List realm which is GenreEntity ?
This should just be a matter of adding the new GenreEntity objects to an array and then return the entire array once done.
This should do it
func convertToList(genreArray: [GenreClass]) -> List<GenreEntityRealmModel> {
let genreEntityList = List<GenreEntityRealmModel>()
genreArray.forEach { model in
let genreEntity = GenreEntity()
genreEntity.gamesCount = model.gamesCount
genreEntityList.append(genreEntity)
}
return genreEntityList
}

How to filter a Model which having nested array in swift

I am having model
class Consumer360PurchaseHistoryDetails: Codable {
var freqofPurchase : String?
var freqofVisit : String?
var customerPurchaseHistory : [CustomerPurchaseHistory]?
init() {
}
}
class CustomerPurchaseHistory : Codable {
var dateOfPurchase : String?
var products : [PurchaseProducts]?
init() {
}
}
class PurchaseProducts : Codable {
var productID : String?
var productFilterType : String?
init() {
}
}
I want to filter this model by productFilterType in PurchaseProducts
I tried the below way
var dataModel: Consumer360PurchaseHistoryDetails?
var tempDataModel:Consumer360PurchaseHistoryDetails = Consumer360PurchaseHistoryDetails()
for purchaseHistory in self.dataModel?.customerPurchaseHistory ?? [] {
for product in purchaseHistory.products ?? [] {
if product.productFilterType?.lowercased() == StringConstants.purchase {
tempDataModel.freqofVisit = "three"
tempDataModel.freqofPurchase = "five"
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
}
}
}
self.purchaseHistoryTableView.dataModel = self.tempDataModel
But the purchaseHistory is not getting appended in customerPurchaseHistory, which is is always nil after appending. But the freqofVisit and freqofPurchase is getting updated. Am i want use index for appending?
Your tempDataModel.customerPurchaseHistory? is set to nil by default. So the below code won't be executed.
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
Just above your for loop, assign its value to empty array, like this:
tempDataModel.customerPurchaseHistory = []
So, your code looks like this:
var dataModel: Consumer360PurchaseHistoryDetails?
var tempDataModel:Consumer360PurchaseHistoryDetails = Consumer360PurchaseHistoryDetails()
tempDataModel.customerPurchaseHistory = []
for purchaseHistory in self.dataModel?.customerPurchaseHistory ?? [] {
for product in purchaseHistory.products ?? [] {
if product.productFilterType?.lowercased() == StringConstants.purchase {
tempDataModel.freqofVisit = "three"
tempDataModel.freqofPurchase = "five"
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
}
}
}
self.purchaseHistoryTableView.dataModel = self.tempDataModel

Array of structs: UserDefaults, how to use?

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.