Better way of iterating through array of custom struct - swift

This is my custom data struct:
struct Absence {
var type: String
var date: TimeInterval
}
I have an array of this data struct like this:
var absences: [Absence]
I would like a function to return all the types of an absence. I have written this:
func types() -> [String] {
var types = [String]()
for absence in self.absences {
if !types.contains(absence.type) {
types.append(absence.type)
}
}
return types
}
I was wondering if there was a better way of iterating through using either map, compactMap or flatMap. I am new to mapping arrays. Any help much appreciated.

You could just do the following:
var types = absences.map { $0.type }
If you would like to filter the types:
var types = absences.map { $0.type }.filter { $0.contains("-") }
Or if you simply want to remove all duplicates:
var types = Array(Set(absences.map { $0.type }))

Related

Swift - How to Change Multi-Level JSON Struct Single Element Value on Condition

I have multi-level Struct that converts complex JSON data to a Struct. What I am struggling is to change the dataStruct.group.point.presentValue element value on condition.
var dataStruct : DataStruct = load("jsonfile.json")
struct DataStruct : Codable {
let name: String
var group: [groupData]
}
struct groupData: Codable, Hashable {
let id, name : String
var point : [pointInfo]
}
struct pointInfo : Codable, Hashable {
let id : String
let address : address
let name : String
var presentValue : String
}
struct address: Codable, Hashable {
let index: String
let type: String
}
I have tried the following map function, but the compiler complains that the Group in ForEach is 'let' constant.
Basically the function is supposed to compare address.index field in the Struct to the passed pointNo variable, and once it has been found (unique), point.presentValue is changed to the new value.
What is the correct way to achieve this?
func updatePresentValue(pointNo : String) {
dataStruct.group.forEach { Group in
Group.point = Group.point.map { point -> pointInfo in
var p = point
if point.address.index == pointNo {
p.presentValue = "New value"
return p
}
else { return p }
}
}
}
Basically there are two ways.
Extract the objects by assigning them to variables, modify them and reassign them to their position in dataStruct.
Enumerate the arrays and modify the objects in place.
This is an example of the second way
func updatePresentValue(pointNo : String) {
for (groupIndex, group) in dataStruct.group.enumerated() {
for (pointIndex, point) in group.point.enumerated() {
if point.address.index == pointNo {
dataStruct.group[groupIndex].point[pointIndex].presentValue = "New value"
}
}
}
}
It gets more complicated when dealing with multilevel structures but here is one way to do it where we first enumerate over group so we get both the object and the index of the object for each iteration so we can use this index when updating the group array. The inner struct is updated using a mutable copy of point
for (index, group) in dataStruct.group.enumerated() {
if group.point.contains(where: { $0.address.index == pointNo }) {
var copy = group
copy.point = group.point.reduce(into: []) {
if $1.address.index == pointNo {
var pointCopy = $1
pointCopy.presentValue = "new value"
$0.append(pointCopy)
} else {
$0.append($1)
}
}
dataStruct.group[index] = copy
}
}

Handling enums with associated structs

I have an array of enums witch contains associated structs like so:
struct TypeOne {
let id = UUID()
var image:UIImage
}
struct TypeTwo {
let id = UUID()
var image:UIImage
var url:URL
}
enum Types {
case one(a: TypeOne)
case two(b: TypeTwo)
}
var array:[Types] = []
Both possible structs share the variables; id and image. Is there any way to retrieve those values without doing like below?
switch array[0] {
case .one(a: let a):
print(a.id)
case .two(b: let b):
print(b.id)
}
Since the two variables are present in both structs I'm looking for a solution like this (if it exists):
print(array[0].(xxx).id)
UPDATE: My answer may miss the mark. Do you really need the enum? In my solution the need for the enum was eliminated. But if you need the enum because it carries other significant info, then my solution is no good. If you are just using the enum so you can put both types in an array, then consider using an array of Typeable instead where Typeable is the protocol of my solution.
Yes, you could use protocols for something like this to define a common set of methods or fields:
protocol Typeable {
var id:UUID {get}
var image:UIImage {get}
}
struct TypeOne:Typeable {
let id = UUID()
var image:UIImage
}
struct TypeTwo:Typeable {
let id = UUID()
var image:UIImage
var url:URL
}
var array:[Typeable] = []
let typeOne:TypeOne = //
let typeTwo:TypeTwo = //
array.append(typeOne)
array.append(typeTwo)
print(array[0].id)
// to declare a function that takes a Typeable
func myMethod<T:Typeable>(val:T) {
print(val.id)
}
Note, my protocol name is no good and doesn't match naming guidelines. Without knowing more about your use case I'm not sure what a good name would be.
Someway or another you're going to have to switch over each case to extract the associated values. However, we can make it convenient for use. I'd start with a protocol so our types have a common interface:
protocol TypeProtocol {
var id: UUID { get }
var image: UIImage { get }
}
struct TypeOne: TypeProtocol {
let id = UUID()
var image: UIImage
}
struct TypeTwo: TypeProtocol {
let id = UUID()
var image: UIImage
var url: URL
}
Your enum can largely be the same but we can extend it with some convenient properties.
enum Types {
case one(a: TypeOne)
case two(b: TypeTwo)
}
extension Types: TypeProtocol {
var id: UUID {
type.id
}
var image: UIImage {
type.image
}
var type: TypeProtocol {
switch self {
case .one(let a):
return a
case .two(let b):
return b
}
}
}
Finally, given an array of types we can use the convenience properties to access the underlying data:
var array: [Types] = []
array.forEach { print($0.id) }
array.forEach { print($0.type.id) }

Simple way to modify deeply nested struct

I've been becoming more familiar with the "copy on write" behavior of Swift structs. I think it's a really nice way to get around having to manage references for structs, but it's a bit cumbersome when dealing with deeply nested structures.
If you want to update a deeply nested value, you need a direct path to that value so you can modify it on a single line:
myStruct.nestedArray[index].nestedValue = 1
The compiler will copy myStruct.nestedArray[index] and set nestedValue to 1 on that new value. It will then copy myStruct.nestedArray and set the new value at index. It will then copy myStruct and replace the previous value with a new one that has all of the above changes.
This works just fine and it's pretty cool that you can do this with a single line of code without having to worry about anything that was referencing myStruct and its children before. However, if there is more complicated logic involved in resolving the path to the value, the logic becomes much more verbose:
struct MyStruct {
var nestedEnum: MyEnum
}
enum MyEnum {
case one([NestedStruct])
case two([NestedStruct])
}
struct NestedStruct {
var id: Int
var nestedValue: Int
}
var myStruct = MyStruct(nestedEnum: .one([NestedStruct(id: 0, nestedValue: 0)]))
if case .one(var nestedArray) = myStruct.nestedEnum {
if let index = nestedArray.firstIndex(where: { $0.id == 0 }) {
nestedArray[index].nestedValue = 1
myStruct.nestedEnum = .one(nestedArray)
}
}
Ideally you'd be able to do something like this:
if case .one(var nestedArray) = myStruct.nestedEnum {
if var nestedStruct = nestedArray.first(where: { $0.id == 0 }) {
nestedStruct.nestedValue = 1
}
}
But as soon as nestedStruct.nestedValue is set, the new value of nestedStruct is swallowed.
What would be nice is if Swift had a way to use inout semantics outside of functions, so I could take a "reference" to nestedArray and then nestedStruct within it and set the inner nestedValue, causing the copy to propagate back up to myStruct the same way as it would if I'd been able to do it in one line.
Does anyone have any nice ways to deal with deeply nested structs that might be able to help me out here? Or am I just going to have to put up with the pattern from my second example above?
The solution I ended up arriving at was pretty SwiftUI specific, but it may be adaptable to other frameworks.
Basically, instead of having a single top-level method responsible for deeply updating the struct, I arranged my SwiftUI hierarchy to mirror the structure of my struct, and passed Bindings down that just manage one node of the hierarchy.
For example, given my struct defined above:
struct MyStruct {
var nestedEnum: MyEnum
}
enum MyEnum {
case one([NestedStruct])
case two([NestedStruct])
}
struct NestedStruct {
var id: Int
var nestedValue: Int
}
I could do this:
struct MyStructView: View {
#Binding var myStruct: MyStruct
var body: some View {
switch myStruct.nestedEnum {
case .one: OneView(array: oneBinding)
case .two: TwoView(array: twoBinding)
}
}
var oneBinding: Binding<[NestedStruct]> {
.init(
get: {
if case .one(array) = myStruct.nestedEnum {
return array
}
fatalError()
},
set: { myStruct.nestedEnum = .one($0) }
)
}
var twoBinding: Binding<[NestedStruct]> { /* basically the same */ }
}
struct OneView: View {
#Binding var array: [NestedStruct]
var body: some View {
ForEach(0..<array.count, id: \.self) {
NestedStructView(nestedStruct: getBinding($0))
}
}
func getBinding(_ index: Int) -> Binding<NestedStruct> {
.init(get: { array[index] }, set: { array[index] = $0 })
}
}
struct NestedStructView: View {
#Binding var nestedStruct: NestedStruct
var body: some View {
NumericInput(title: "ID: \(nestedStruct.id)", value: valueBinding)
}
var valueBinding: Binding<Int> {
.init(get: { nestedStruct.value }, set: { nestedStruct.value = $0 })
}
}
The only annoying bit is that it can be a bit verbose to construct a Binding manually. I wish SwiftUI had some syntax for getting nested Bindings from a Binding containing an array or struct.

Observing a #Published var from another Object

I am trying to get one object to listen to changes in the property of another object. I have it working as shown below, but I would prefer the observing object knew nothing of the Model, just the property.
class Model : ObservableObject{
#Published var items: [Int] = []
}
class ObjectUsingItems{
var itemObserver: AnyCancellable?
var items: [Int] = []
func observeItems(model: Model){
itemObserver = model.$items
.sink{ newItems in
self.items = newItems
print("New Items")
}
}
}
At the moment I begin observing the model.items as follows - which works:
let model = Model()
let itemUser = ObjectUsingItems()
itemUser.observeItems(model: model)
model.items.append(1) // itemUser sees changes
Unfortunately I can’t seem to figure out just what is required as the parameter to the observeItems method so that it works without knowing anything about the Model - like this:
class ObjectUsingItems{
var itemObserver: AnyCancellable?
var items: [Int] = []
func observeItems(propertyToObserve: WhatGoesHere?){
itemObserver = propertyToObserve
.sink{ newItems in
// etc.
}
}
}
And then call it like so:
itemUser.observeItems(XXX: model.$items)
Can anyone explain what I need to do? Thanks!
You can just accept a publisher as a parameter, if you don't care where the value comes from.
In your very specific case, it could be:
func observeItems(propertyToObserve: Published<[Int]>.Publisher) {
itemObserver = propertyToObserve
.sink { self.items = $0 }
}
But this might be too restrictive - why only this specific publisher? In principle, you shouldn't care what the publisher is - all you care about is the output value and error type. You can make it generic to any publisher, so long as its Output is [Int] and Failure is Never (like that of the #Published one):
func observeItems<P: Publisher>(propertyToObserve: P)
where P.Output == [Int], P.Failure == Never {
itemObserver = propertyToObserve
.sink { self.items = $0 }
}
The usage would be something like this:
let model = Model()
let itemUser = ObjectUsingItems()
itemUser.observeItems(propertyToObserve: model.$items)

Swift 4 KeyPath On Objects of Different Type

I have 2 types, A and B that implement the same methods and have the same properties on them. I have defined an extension to fetch a value in a sub property for each of A and B. I want to know if there is a way to reduce those 2 extensions down to 1 method. Imagine there are many more types like A and B so the code duplication problem becomes much worse.
Update: A and B are generated along with many others like them. The original plan is to avoid writing extensions at all for A or B. I don't know if this is possible but I was told I could use KeyPaths for this. The properties names must be different. This is a byproduct of the code generation
struct A {
var something: Common
}
struct B {
var somethingElse: Common
}
struct Common {
var value1: String
var value2: String
}
extension A {
func valueFor(condition: Bool) -> String {
return condition ? self.something.value1 : self.something.value2
}
}
extension B {
func valueFor(condition: Bool) -> String {
return condition ? self.somethingElse.value1 : self.somethingElse.value2
}
}
I think protocols are the solution for your problem. They help to make code more generic.
protocol CommonContaining {
var common: Common { get set }
func valueFor(condition: Bool) -> String {
return condition ? self.common.value1 : self.common.value2
}
}
struct A {
var something: Common
}
struct B {
var somethingElse: Common
}
extension A: CommonContaining {
var common: Common {
return something
}
}
extension B: CommonContaining {
var common: Common {
return somethingElse
}
}
From what i have understand, if the method is related to Common struct then you should implement this method in the struct itself or to create extension to the struct :
struct A
{
var something: Common
}
struct B
{
var somethingElse: Common
}
struct Common
{
var value1: String
var value2: String
func valueFor(condition: Bool) -> String {
return condition ? self.value1 : self.value2 }
}
var object_1 = A(something: Common(value1: "1", value2: "1"))
var object_2 = B(somethingElse: Common(value1: "1", value2: "2"))
print(object_1.something.valueFor(condition: true))
print(object_2.somethingElse.valueFor(condition: false))
Good luck.