Is there a way to make this function generic? - swift

I have a data set:
struct MyData {
var name: String
var date: Date
var quantity: Int
var inStock: Bool
}
That is then used in an array
let myArray: [MyData] = [...]
And I'm currently writing methods to change values..
func changeName(to newName: String) { myArray[0].name = newName }
func changeDate(to newDate: Date) { myArray[0].date = newDate }
func changeQuantity(to newQuantity: Int) { myArray[0].quantity = newQuantity}
And I start thinking that this would be a perfect case for generics and say something like
func changeValue<Value>(to newValue: Value) { ... }
only problem is I can't make the dot notation generic
If I could say
myArray[0].value = newValue
Any guidance on the matter would be helpful!

If you var declare your properties you can add the following function to your struct
mutating func setValue<Value>(_ value: Value, for keyPath: WritableKeyPath<Self, Value>) {
self[keyPath: keyPath] = value
}
And then use it like
var myData = MyData(name: "abs", date: Date(timeIntervalSince1970: 0), quantity: 0, inStock: false)
myData.setValue(500, for: \.quantity )
myData.setValue(Date(), for: \.date)
myData.setValue(true, for: \.inStock)
which will update the object
MyData(name: "abs", date: 2021-03-27 18:26:26 +0000, quantity: 500, inStock: true)
Then how you want to use it for your array is a bit unclear to me but here you at least have a tool to work with.

Related

Filter an Array of Structs by Months? SwiftUI

Hey guys I got something very similar going on in my project. But I was wondering when you have an array of structs, how would you be able to filter for items in specific month? Also the date is in epoch form.
import SwiftUI
struct TestStruct {
var food: String
var date: Double
}
class Test: ObservableObject{
let food1 = TestStruct(food: "Hamburger", date: 1641058794)
let food2 = TestStruct(food: "HotDog", date: 1651426794)
let food3 = TestStruct(food: "icecream", date: 1652204394)
let foodForYear: [TestStruct] = [food1, food2, food3]
}
You can achieve that by using a simple filter on the array, and getting the TimeInterval you need in order to do the filter, here is a simple example:
import Foundation
struct TestStruct {
var food: String
var date: Double
var parsedDate: Date {
Date(timeIntervalSince1970: date)
}
var monthNumber: Int {
Calendar.current.component(.month, from: parsedDate)
}
}
class Test: ObservableObject{
let food1 = TestStruct(food: "Hamburger", date: 1641058794)
let food2 = TestStruct(food: "HotDog", date: 1651426794)
let food3 = TestStruct(food: "icecream", date: 1652204394)
let foodForYear: [TestStruct] = [food1, food2, food3]
func filterMonthy() {
let fiteredByMonths = foodForYear.filter({$0.monthNumber == 1}) // any logic you want to apply
print(fiteredByMonths)
}
}
Take a look at the documentation: Date
The issue is that you are using epoch form itself instead of native Date:
struct TestStruct {
var food: String
var date: Date
init(food: String, date: Double) {
self.food = food
self.date = Date(timeIntervalSince1970: date)
}
}
In this case you can easy get month or year in a string format:
extension TestStruct {
var monthName: String {
return date.formatted(
Date.FormatStyle()
.year(.defaultDigits) // remove if you don't need it
.month(.abbreviated) // you can choose .wide if you want other format or read help for more information
)
}
}
Now let's check it:
let foodForYear: [TestStruct] = ...
print(foodForYear.sorted{ $0.monthName > $1.monthName}) // sorting
let nameOfMonth = "SomeMonthName" // depends from your format style
print(foodForYear
.compactMap{
if $0.monthName.contains(nameOfMonth) {
return $0
} else { return nil }
}
)

How can i disable adding new item to an array in Swift?

I have an array like this inside a struct:
struct TestType {
private(set) var array: [String] = ["0", "1", "2"]
mutating func updateItem0(_ value: String) {
self.array[0] = value
}
mutating func updateItem1(_ value: String) {
self.array[1] = value
}
mutating func updateItem2(_ value: String) {
self.array[2] = value
}
}
I want be able to disable appending method to this array when I use an instance of struct, and keeping the count of it as it is. I cannot use private(set) because it would not allow me to update items of it.
My idea is using private(set) inside struct and making a mutating function for updating items in case, I wanted to know if there is better way for it?
Lots of options but a simple enhancement would be passing the index:
mutating func update(_ value: String, at index: Int) {
array[index] = value
}
And another is to check if the operation is possible:
enum Error: Swift.Error {
case indexOutOfBound
}
mutating func update(_ value: String, at index: Int) throws {
guard array.indices.contains(index) else { throw Error.indexOutOfBound }
array[index] = value
}
Here is a nice way to handle it. Add subscript to your struct which then allows you to access and change the values like you would an array. Adopting CustomStringConvertible and implementing description allows you to print the internal array while keeping it entirely private:
struct TestType: CustomStringConvertible {
private var array: [String] = ["0", "1", "2"]
var description: String { String(describing: array) }
subscript(_ index: Int) -> String {
get {
return array[index]
}
set {
array[index] = newValue
}
}
}
var foo = TestType()
foo[0] = "hello"
foo[2] = "goodbye"
foo[3] = "oops" // Fatal error: Index out of range
print(foo[0]) // hello
print(foo[1]) // 1
print(foo[2]) // goodbye
print(foo) // ["hello", "1", "goodbye"]

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"]

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.

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.