I am writing an iOS App in Swift 4.2
I am looping through array 1 and making changes to its one element and adding those elements to array 2.
Array:
let array:[model] = []
let array2:[model] = []
Model for this array is:
class model: NSObject {
var id:Int = 0
var name:String=""
var isChecked:Bool=false
}
Function:
func customCheckboxValueChangedWithID(_ id: Int, isChecked: Bool) {
array2.removeAll()
//TODO, use sugar syntax/higher order function for this loop:
for element in array{
if(element.id==id){
element.isChecked=isChecked
if(element.isChecked){
array2.append(element)
}
}
}
reloadCollectionView()
}
How to convert this into swift higher order function, like map, flatMap, etc?
You can do it this way:
array.forEach { if $0.id == id { $0.isChecked.toggle() } }
If you just want to filter the elements and only leave the elements whose isChecked property set to true, you can use this:
let newArray = array.filter { $0.isChecked }
Since you have an array of classes, this is very simple:
let array: [model] = [...]
array.first { $0.id == id }?.isChecked = isChecked
If you had an array of structs, you would either need to update the original array or create a whole new array:
var array: [model] = []
// updating element in the original array
if let index = array.index(where: { $0.id == id }) {
array[index].isChecked = isChecked
}
// updating the whole array
array = array.map {
guard $0.id == id else { return $0 }
var element = $0
element.isChecked = isChecked
return element
}
You can rewrite this like...
array.forEach {
if $0.id == someID { $0.isChecked = isChecked }
}
This only works because you are using classes.
If you switch to using structs then you could have...
let newArray = array.map {
if $0.if == someID {
var theModel = $0
theModel.isChecked = isChecked
return theModel
}
return $0
}
how about using 'flatMap'
here is playground source
//: Playground - noun: a place where people can play
import UIKit
struct model {
var id:Int = 0
var name:String=""
var isChecked:Bool=false
}
let array:[model] = [model(id: 0, name: "0", isChecked: false),
model(id: 1, name: "1", isChecked: false),
model(id: 2, name: "2", isChecked: false),
model(id: 3, name: "3", isChecked: false),
model(id: 4, name: "4", isChecked: false),
model(id: 5, name: "5", isChecked: false),
model(id: 6, name: "6", isChecked: false)
]
var array2:[model] = []
func customCheckboxValueChangedWithID(_ id: Int, isChecked: Bool) {
array2 = array.flatMap { (ma: model) -> model? in
var element = ma
if element.id == id {
element.isChecked = isChecked
return element
}
return nil
}
}
customCheckboxValueChangedWithID(1, isChecked: true)
print("array2 : \(array2)")
result:
array2 : [__lldb_expr_53.model(id: 1, name: "1", isChecked: true)]
You can use Filter
numbers.forEach({ if $0.id == id { $0.isChecked = isChecked } })(Copied SPatel's comment)
Loops over a collection and returns an array that contains elements that meet a condition.
Let’s assume we need to filter even numbers only in the following array example. The traditional approach can be:
let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15]
// The traditional way
var evenNumArray: [Int] = []
for number in numbersArray {
if number % 2 == 0 {
evenNumArray.append(number)
}
}
// evenNumArray = [2, 4, 6, 8, 10, 14]
Instead, let’s use filter for the same result:
let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15]
// Filter
let evenArray = numbersArray.filter { $0 % 2 == 0 }
Simple Higher Order Functions in Swift
You can try this:
class MyObj: CustomStringConvertible {
var id: String
var isChecked: Bool
init(id: String = "", isChecked: Bool = false) {
self.id = id
self.isChecked = isChecked
}
var description: String {
return "MyObj(id: \(id), isChecked: \(isChecked)"
}
}
let array = [
MyObj(id: "id1"),
MyObj(id: "id1", isChecked: true),
MyObj(id: "id2"),
MyObj(id: "id3", isChecked: true)
]
func customCheckboxValueChanged(_ values: [MyObj], id: String, isChecked: Bool) -> [MyObj] {
let sameIDModels: [MyObj] = values
.filter { $0.id == id }
.map { $0.isChecked = isChecked; return $0 }
return sameIDModels.filter{ $0.isChecked }
}
let isChecked = true
let myID = "id1"
print(customCheckboxValueChanged(array, id: myID, isChecked: isChecked))
Related
I have a safe subscript extension on Array to give me an ArraySlice from a Range like this:
extension Array {
subscript(safe range: Range<Index>) -> ArraySlice<Element>? {
if range.endIndex > endIndex {
if range.startIndex >= endIndex {
return nil
} else {
return self[range.startIndex..<endIndex]
}
} else {
return self[range]
}
}
}
I have a use case for needing a slice of a binding of an array such that I can do this:
let userSlice = $users[safe: 0..<20]
This should return Slice<Binding<[User]>>. How can I alter the above code to work with a Binding of an Array?
Here is a simplified demo of possible approach - at first, and main, is that binding requires wrapped value to be mutable, and at second binding, actually, has no sense on optional (about this see below alternate way).
Tested with Xcode 13.2 / iOS 15.2
extension Array {
subscript(safe range: Range<Index>) -> ArraySlice<Element>? {
get {
if range.endIndex > endIndex {
if range.startIndex >= endIndex {
return nil
} else {
return self[range.startIndex..<endIndex]
}
} else {
return self[range]
}
}
mutating set { // << add mutability !!
if let value = newValue {
_ = value.indices.map { self[$0] = value[$0] }
}
}
}
}
struct DemoView1: View {
struct Model: Identifiable {
let id: Int
var value: String
}
#State private var users = [
Model(id: 1, value: "1"), Model(id: 2, value: "2"), Model(id: 3, value: "3"),
Model(id: 4, value: "4"), Model(id: 5, value: "5")]
var body: some View {
VStack {
Text("Array slice")
List {
// Optional has not sense, so we must explicitly remove
// it that makes code more complicated and less readable
ForEach(Binding($users[safe: 1..<3]) ?? .constant(ArraySlice<Model>()), id: \.wrappedValue.id) {
TextField("", text: $0.value)
}
}
Divider()
Text("Full Array")
List {
ForEach(users) {
Text($0.value)
}
}
}
}
}
Alternate: due to binding has not sense for optionals and non-optional type is better controlled, I would recommend, as alternate, to consider possibility to avoid optional and use subscript with returned definite data, like
extension Array {
subscript(safe range: Range<Index>) -> ArraySlice<Element> {
get {
if range.endIndex > endIndex {
if range.startIndex >= endIndex {
// Here can be something known and defined
return ArraySlice<Element>() // << just empty !!
} else {
return self[range.startIndex..<endIndex]
}
} else {
return self[range]
}
}
mutating set {
_ = newValue.indices.map { self[$0] = newValue[$0] }
}
}
}
struct DemoView2: View {
struct Model: Identifiable {
let id: Int
var value: String
}
#State private var users = [
Model(id: 1, value: "1"), Model(id: 2, value: "2"), Model(id: 3, value: "3"),
Model(id: 4, value: "4"), Model(id: 5, value: "5")]
var body: some View {
VStack {
Text("Array slice")
List {
// Now code is simple and clear !!
ForEach($users[safe: 1..<3], id: \.wrappedValue.id) {
TextField("", text: $0.value)
}
}
Divider()
Text("Full Array")
List {
ForEach(users) {
Text($0.value)
}
}
}
}
}
I am trying to extract just the id's from the Properties Array to form a separate array. So far I have come up with this:
struct Interactions: View {
#State var A: [Properties] = [
.init(id: 5, name: "Five"),
.init(id: 8, name: "Eight"),
.init(id: 2, name: "Two")
]
var body: some View {
List(loadIdArray(), id: \.self) { i in
Text("\(i)")
}
}
func loadIdArray() -> [Int] {
let ids: [Int] = [1, 2]
for i in 0 ..< self.A.count {
let ids = [self.A[i].id, self.A[i + 1].id]
return ids
}
return ids
}
}
The problem is that I would have to manually type each [self.A[I + ...].id] which defeats the purpose of the function.
The answer I want is for loadIdArray() = [5, 8, 2] and for it to do this automatically depending on how many items are in the 'A' array.
how about using this:
func loadIdArray() -> [Int] {
return self.A.map { $0.id }
}
I have an object array with duplicated object properties value for some objects and I need to filter it according to some object properties conditions
I have something similar to the following code:
Model:
struct MyObject {
var name: String
var id: String
var times: Int
var value: Double
}
Array:
Let objectArray = [
MyObject(name: “Obj1”, id: “123”, times: 1, value: 3.0),
MyObject(name: “Obj2”, id: “456”, times: 1, value: 2.3),
MyObject(name: “Obj3”, id: “789”, times: 1, value: 1.0),
MyObject(name: “Obj2”, id: “456”, times: 2, value: 3.3),
MyObject(name: “Obj2”, id: “456”, times: 3, value: 4.7),
MyObject(name: “Obj4”, id: “212”, times: 1, value: 2.4)
]
I have implemented an extension Array function to remove the duplicated items in array but it's not enough on this case:
func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element] {
var uniqueKeys = Set<String>()
return filter{ uniqueKeys.insert("\(keyValue($0))").inserted }
}
I need to filter objectArray to obtain an array without duplicated objects with the condition to maintain the highest value for times property, that means, the final result should be:
Expected result:
filteredObjectArray = [
MyObject(name: “Obj1”, id: “123”, times: 1, value: 3.0),
MyObject(name: “Obj3”, id: “789”, times: 1, value: 1.0),
MyObject(name: “Obj2”, id: “456”, times: 3, value: 4.7),
MyObject(name: “Obj4”, id: “212”, times: 1, value: 2.4)
]
With Obj2 repeated item filtered by times property = 3
Here is a solution using reduce(into:) to filter out the unique objects
let uniqueAndHighest = objectArray.reduce(into: [MyObject]()) { array, object in
if let index = array.firstIndex(where: { $0.id == object.id }) {
if array[index].times < object.times {
array[index] = object
}
} else {
array.append(object)
}
}
and an alternative solution using Dictionary(grouping:)
let uniqueAndHighest = Dictionary(grouping: objectArray) {$0.id}
.values
.compactMap { $0.max {$0.times < $1.times}}
You can use reduce in this case:
let resultArray = objectArray.reduce([]) { (nextResult, obj) -> [MyObject] in
var tempArray = nextResult
if let existObj = tempArray.first(where: { $0.name == obj.name && $0.id == obj.id }) {
if obj.times > existObj.times {
tempArray.removeAll(where: { $0.name == existObj.name })
tempArray.append(obj)
}
} else {
tempArray.append(obj)
}
return tempArray
}
I need to change values in array of object
Array of selected sports which is received from API
Update API Response
arrSelectedID = [["slug": cricket,
"id": 1,
"banner_image": https://_1558964043.jpg,
"name": Cricket,
"icons": {
black = "https://sport_icon_cricket_black.png";
green = "https://sport_icon_cricket_green.png";
grey = "https://sport_icon_cricket_gray.png";
white = "https://sport_icon_cricket_white.png";
}],
["slug": soccer,
"banner_image": https://1558964051.jpg
"icons": {
black = "https://sport_icon_soccer_black.png";
green = "https://sport_icon_soccer_green.png";
grey = "https://sport_icon_soccer_gray.png";
white = "https://sport_icon_soccer_white.png";
},
"id": 2,
"name": Soccer]]
My Array
struct ObjSportsList:Codable {
var id:Int
var name:String
var slug:String
var selected:Bool?
var icons:ObjSportsIcon
}
struct ObjSportsIcon:Codable {
var green:String
var grey:String
var white:String
var black:String
}
var arrSports:[ObjSportsList] = [] // Array which is custom object
Below response is printed my custom object "ObjSportsList"
arrSports = (id: 1,
name: "Cricket",
slug: "cricket",
selected: nil,
),
(id: 2,
name: "Soccer",
slug: "soccer",
selected: nil,
),
(id: 3,
name: "Baseball",
slug: "baseball",
selected: nil,
),
I want to change "selected" values = true which is get id from API array in "data" Example : [1,3] only change 1st and 3rd selected values == true
I try to work with below code but array not updated
for (index, var sports) in self.arrSports.enumerated() {
for selectedSports in arrSelectedID {
if selectedSports["id"] as! Int == sports.id {
sports.selected = true
}
}
Please refer below code to replace the specific value in Array of object
let aryIDS = [1,3]
for (index, sport) in arrSports.enumerated(){
if aryIDS.contains(sport.id) {
arrSports[index].selected = true
}
}
print(arrSports) //Received updated array
I fix the issue with my own code, Please check this code
for (index, sports) in self.arrSports.enumerated() {
for selectedSports in arrSelectedID {
if selectedSports["id"] as! Int == sports.id {
self.arrSports[index].selected = true
}
}
}
I've got the below struct and would like to sort the items within sessions by startTime field. I'm completely lost on how to do this.
I tried:
let sortedArray = sessionsData?.items.sorted{ ($0["startTime"] as! String) < ($1["startTime"] as! String) }
but that just gives me an error about no subscript members?
Any pointers would really be appreciated, thank you.
public struct sessions: Decodable {
let status: String?
let start: Int?
let count: Int?
let items: [sessionInfo]?
let itemsCount: Int?
let multipart: Bool?
let startTime: Int?
let endTime: Int?
}
public struct sessionInfo: Decodable {
let name: String?
let datalist: String?
let sessionType: Int?
let status: Int?
let backupType: Int?
let startTime: Int?
let endTime: Int?
let owner: String?
let numOfErrors: Int?
let numOfWarnings: Int?
let flags: Int?
}
I tried the below, but get an error:
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in
return lhs.startTime < rhs.startTime
})
error:
Binary operator '<' cannot be applied to two 'Int?' operands
Try below code, it sorts the struct in asc order but it pushes nil timestamps to bottom.
if you want nil timestamps to be at top, make all nil checks in below code to return opposite of what i return in below code.
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in
if let lhsTime = lhs.startTime, let rhsTime = rhs.startTime {
return lhs.startTime < rhs.startTime
}
if lhs.startTime == nil && rhs.startTime == nil {
// return true to stay at top
return false
}
if lhs.startTime == nil {
// return true to stay at top
return false
}
if rhs.startTime == nil {
// return false to stay at top
return true
}
})
You should access the fields directly and not through subscripts.
let sortedArray = sessionsData?.items.sorted(by: {$0.startTime < $1.startTime})
You could write this (tested in playground) :
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in return (lhs.startTime ?? 0) < (rhs.startTime ?? 0) })
If one is optional, you do not crash, even though result of comparison is meaningless
You have default High Order Function for sorting the struct array in ascending and descending order
Example
let roster: [TeamMember] = [.init(id: 1, name: "Abishek", age: 19),
.init(id: 2, name: "Dinesh", age: 22),
.init(id: 3, name: "Praveen", age: 24),
.init(id: 4, name: "Sam", age: 25),
.init(id: 5, name: "David", age: 21)]
let descendingSorted = roster.sorted{$0.name > $1.name} // for descending order
let ascendingSorted = roster.sorted{$0.name < $1.name} // for ascending order
print(descendingSorted)
print(ascendingSorted)
Your Output
// for descending order
[TeamMember(id: 4, name: "Sam", age: 25.0),
TeamMember(id: 3, name: "Praveen", age: 24.0),
TeamMember(id: 2, name: "Dinesh", age: 22.0),
TeamMember(id: 5, name: "David", age: 21.0),
TeamMember(id: 1, name: "Abishek", age: 19.0)]
// for ascending order
[TeamMember(id: 1, name: "Abishek", age: 19.0),
TeamMember(id: 5, name: "David", age: 21.0),
TeamMember(id: 2, name: "Dinesh", age: 22.0),
TeamMember(id: 3, name: "Praveen", age: 24.0),
TeamMember(id: 4, name: "Sam", age: 25.0)]
And we have another one method for sorting is SortComparator. we sort the result based on ComparisonResult
let descendingSorted1 = roster.sorted { teamMember1, teamMember2 in
return teamMember1.name.compare(teamMember2.name) == .orderedDescending
} // for descending order
let ascendingSorted1 = roster.sorted{ teamMember1, teamMember2 in
return teamMember1.name.compare(teamMember2.name) == .orderedAscending
} // for ascending order
print(descendingSorted1)
print(ascendingSorted1)