I have a class with 2 property
class SelectedAmount : Serializable {
lateinit var amount: List<Int> //the values here is coming from backend in array list. eg [50,100,150,200]
var isSelect: Boolean = false
}
I want to pair each amount with a boolean value. eg [{50, true}, {100, false}, {150, false}, {200, false}]
In view activity i did
private var amountList: MutableList<AmountSelected> = ArrayList<AmountSelected>()
val amountInterval = data.reloadDenoms // BE value {50,100,150,200}
if (amountInterval != null) {
for (items in amountInterval) {
var amountSelected:SelectedAmount = SelectedAmount()
amountSelected.amount = amountInterval
amountSelected.isSelect = false // tring to set boolean false for every amountInterval value
amountList.add(amountSelected)
}
when i tring to print the value of amountList .. i get out put as
[{50,100,150,200}, false]
my expected output is
[{50, true}, {100, false}, {150, false}, {200, false}]
can anyone help me on this? I am a newbie here learning array btw
No need of List of integers in SelectedAmount
class SelectedAmount : Serializable {
lateinit var amount: int //the values here is coming from backend in array list. eg [50,100,150,200]
var isSelect: Boolean = false
}
And
// *** Note the SelectedAmount instead of AmountSelected
private var amountList: MutableList<SelectedAmount> = ArrayList<SelectedAmount>()
val amountInterval = data.reloadDenoms // BE value {50,100,150,200}
if (amountInterval != null) {
for (items in amountInterval) {
var amountSelected:SelectedAmount = SelectedAmount()
amountSelected.amount = items // **Add only the integer** You were adding the list earlier
amountSelected.isSelect = true // **You can set boolean for one or more items if needed**
amountList.add(amountSelected)
}
The easier approach I got
listOf(50,100, 150, 200).map {
it to (it % 100 != 0)
}.let {
print(it)
}
Related
This is my data structure
struct SPPWorkout: Codable {
static let setKey = "Sets"
static let exerciseID = "id"
var id: Double? = 0.0
var duration: String?
var calories: Int?
var date: String?
var exercises: [ExerciseSet]
[...]
}
struct ExerciseSet: Codable {
let id: String
let name: String
var reps: Int
var weight: Double
[...]
}
extension ExerciseSet: Equatable {
static func ==(lhs: ExerciseSet, rhs: ExerciseSet) -> Bool {
return lhs.id == rhs.id
}
}
and in a SwiftUI view I'm trying to modify an ExerciseSet from user input
#State private var sppWorkout: SPPWorkout!
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ?? 0.0
self.editingIndex = nil
})
}
The issue is here
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ??
and I've tried in all ways to update it, both from the view and with a func in SPPWorkout. I've also tried to replace the object at index
var newSet = ExerciseSet(id: [...], newValues)
self.exercises[editingIndex] = newSet
but in no way it wants to update. I'm sure that somewhere it creates a copy that it edits but I have no idea why and how to set the new values.
Edit: if I try to delete something, it's fine
sppWorkout.exercises.removeAll(where: { $0 == sppWorkout.exercises[index]})
Edit 2:
It passes the guard statement and it does not change the values in the array.
Edit 3:
At the suggestion below from Jared, I've copied the existing array into a new one, set the new values then tried to assign the new one over to the original one but still, it does not overwrite.
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
print(sppWorkout.exercises)
guard let editingIndex = editingIndex else { return }
var copyOfTheArray = sppWorkout.exercises
copyOfTheArray[editingIndex].reps = Int(reps) ?? 0
copyOfTheArray[editingIndex].weight = Double(weight) ?? 0.0
//Copy of the array is updated correctly, it has the new values
sppWorkout.exercises = copyOfTheArray
//Original array doesn't get overwritten. It still has old values
self.editingIndex = nil
Edit 4: I've managed to make progress by extracting the model into a view model and updating the values there. Now the values get updated in sppWorkout, but even though I call objectWillChange.send(), the UI Update doesn't trigger.
full code:
class WorkoutDetailsViewModel: ObservableObject {
var workoutID: String!
#Published var sppWorkout: SPPWorkout!
func setupData(with workoutID: String) {
sppWorkout = FileIOManager.readWorkout(with: workoutID)
}
func update(_ index: Int, newReps: Int, newWeight: Double) {
let oldOne = sppWorkout.exercises[index]
let update = ExerciseSet(id: oldOne.id, name: oldOne.name, reps: newReps, weight: newWeight)
sppWorkout.exercises[index] = update
self.objectWillChange.send()
}
}
struct WorkoutDetailsView: View {
var workoutID: String!
#StateObject private var viewModel = WorkoutDetailsViewModel()
var workout: HKWorkout
var dateFormatter: DateFormatter
#State private var offset = 0
#State private var isShowingOverlay = false
#State private var editingIndex: Int?
#EnvironmentObject var settingsManager: SettingsManager
#Environment(\.dismiss) private var dismiss
var body: some View {
if viewModel.sppWorkout != nil {
VStack {
ListWorkoutItem(workout: workout, dateFormatter: dateFormatter)
.padding([.leading, .trailing], 10.0)
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
let index = viewModel.sppWorkout.exercises.firstIndex(of: exercise) ?? 0
DetailListSetItem(exerciseSet: viewModel.sppWorkout.exercises[index], set: index + 1)
.environmentObject(settingsManager)
.swipeActions {
Button(role: .destructive, action: {
viewModel.sppWorkout.exercises.removeAll(where: { $0 == viewModel.sppWorkout.exercises[index]})
} ) {
Label("Delete", systemImage: "trash")
}
Button(role: .none, action: {
isShowingOverlay = true
editingIndex = index
} ) {
Label("Edit", systemImage: "pencil")
}.tint(.blue)
}
}
.padding([.leading, .trailing], -30)
//iOS 16 .scrollContentBackground(.hidden)
}
.overlay(alignment: .bottom, content: {
editOverlay
.animation(.easeInOut (duration: 0.5), value: isShowingOverlay)
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
do {
try FileIOManager.write(viewModel.sppWorkout, toDocumentNamed: "\(viewModel.sppWorkout.id ?? 0).json")
} catch {
Debugger.log(error: error.localizedDescription)
}
dismiss()
}){
Image(systemName: "arrow.left")
})
} else {
Text("No workout details found")
.italic()
.fontWeight(.bold)
.font(.system(size: 35))
.onAppear(perform: {
viewModel.setupData(with: workoutID)
})
}
}
#ViewBuilder private var editOverlay: some View {
if isShowingOverlay {
ZStack {
Button {
isShowingOverlay = false
} label: {
Color.clear
}
.edgesIgnoringSafeArea(.all)
VStack{
Spacer()
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
print(viewModel.sppWorkout.exercises)
print("dupa aia:\n")
viewModel.update(editingIndex, newReps: Int(reps) ?? 0, newWeight: Double(weight) ?? 0.0)
print(viewModel.sppWorkout.exercises)
self.editingIndex = nil
})
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color("popupBackground"),
lineWidth: 3)
)
}
}
}
}
}
So I got a very good explanation on reddit on what causes the problem. Thank you u/neddy-seagoon if you are reading this.
The explanation
. I believe that updating an array will not trigger a state update. The only thing that will, with an array, is if the count changes. So
sppWorkout.exercises[index].reps = newReps
will not cause a trigger. This is not changing viewModel.sppWorkout.exercises.indices
So all I had to to was modify my List from
List(viewModel.sppWorkout.exercises, id: \.id)
to
List(viewModel.sppWorkout.exercises, id: \.hashValue)
as this triggers the list update because the hashValue does change when updating the properties of the entries in the list.
For the line
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
Replace with
List(viewModel.sppWorkout.exercises, id: \.self) { exercise in
#State private var myBoxArray = Array<Box>()
#State private var amountEligibleForSelection:Int = 0
struct Item: Hashable {
var isCool:Bool? = nil
init(_ isCool:Bool){
self.isCool = isCool
}
}
struct Box: Hashable {
var boxNumber:Int? = nil
var contents:Item? = nil
var isSelected:Bool = false
var revealBoxContents:Bool = false
init(_ contents:Item, _ boxNumber:Int) {
self.contents = contents
self.boxNumber = boxNumber
}
}
func SetupTheBoxs(_ howManyItems:Int) -> Array<Box> {
var preparedBoxs = Array<Box>()
var boxCounter = 0
if boxCounter > howManyItems {
preparedBoxs.append(Box(Item(false), boxCounter-1))
}
print(preparedBoxs)
return preparedBoxs
}
myBoxArray = SetupTheBoxs(3)
func SelectAllBoxes(){
for box in myBoxArray{
if box.isSelected == false && box.contents!.isCool == false {
box.isSelected = true
Error: Cannot assign to property: 'Box' is a 'let' constant
}
}
}
The state of the properties within myBoxArray triggers UIViews in my app, so I need these values to change and trigger. Why can't I change the property value?
Things I've tried:
Using a binding variable for myBoxArray (FAIL)
Added a mutating function to the Box struct to flip the 'isSelected':bool (FAIL)
Setting the function logic as a button action (FAIL)
Someone please help me. #crying
You have to mutate the property that is marked with #State wrapper and not the separate instance of Box within your view. Try something like.
func SelectAllBoxes(){
for index in 0..<myBoxArray.count{
if myBoxArray[index].isSelected == false && myBoxArray[index].contents!.isCool == false {
myBoxArray[index].isSelected = true
}
}
}
I'd like to properly save inputted user data / variables inside an array which is inside a struct but unable to get it to work.
Currently, I have a few structs:
struct Household: Codable {
let id = UUID()
var region: String
var householdSize: Int = 1
var receivingBenefits: [String]
var energyCrisis: Bool
var utilityProviders: [String]
var residenceType: String
var propertyTaxPastDue, homeNeedsRepairs, filedPreviousYearTaxReturn, heatingSystemNeedsRepairs: Bool
var atRiskOfHomelessness: Bool
var receivedMaximumBenefit: ReceivedMaximumBenefit
var personDetails: [PersonDetail]
var incomes: [Income]
var assets: [Asset]
enum CodingKeys: String, CodingKey {
case region
case householdSize = "household_size"
case receivingBenefits = "receiving_benefits"
case energyCrisis = "energy_crisis"
case utilityProviders = "utility_providers"
case residenceType = "residence_type"
case propertyTaxPastDue = "property_tax_past_due"
case homeNeedsRepairs = "home_needs_repairs"
case filedPreviousYearTaxReturn = "filed_previous_year_tax_return"
case heatingSystemNeedsRepairs = "heating_system_needs_repairs"
case atRiskOfHomelessness = "at_risk_of_homelessness"
case receivedMaximumBenefit = "received_maximum_benefit"
case personDetails = "person_details"
case incomes, assets
}
}
struct PersonDetail: Codable, Identifiable {
let id = UUID() // <-- here
var age: Int = 18
var maritalStatus: String = ""
var minimumEmploymentOverExtendedPeriod: Bool
var workStatus: String = ""
var pregnant: Bool
var attendingSchool: Bool = false
var disabled: Bool
enum CodingKeys: String, CodingKey {
case age
case maritalStatus = "marital_status"
case minimumEmploymentOverExtendedPeriod = "minimum_employment_over_extended_period"
case workStatus = "work_status"
case pregnant
case attendingSchool = "attending_school"
case disabled
}
}
class Base: ObservableObject, Codable {
#Published var household: Household
enum CodingKeys: String, CodingKey {
case household = "household"
}
}
Now, I can easily bind a Textfield, toggle or Picker to anything under the Household struct for example below which I believe is easily connected via household in the Base() class..
HStack() {
Image(systemName:"wrench.and.screwdriver.fill")
.frame(width: 15, height: 15)
Toggle(isOn: $eligBase.household.homeNeedsRepairs) {
Text("Need Home Repairs?")
.font(.system(size: 15))
}.tint(.blue)
}
However, I'm unable to connect anything in the array`[PersonDetail], which I included the struct.
For example, If I wanted to connected a Toggle for the disabled variable in `PersonDetail', I get an error, here is my disabled toggle:
Toggle(isOn: $eligBase.household.personDetails.disabled) {
Text("Disabled")
.font(.system(size: 15))
......
}
I receive an error stating:
Value of type 'Binding<[PersonDetail]>' has no dynamic member
'disabled' using key path from root type '[PersonDetail]'
Any ideas how I can connect Toggles, Textfield, Picker to a variable in an array which is in a struct?
You can use a List for your array of PersonDetail, such as in this example code:
EDIT:
struct ContentView: View {
#StateObject var eligBase = Base()
var body: some View {
List {
let _ = print("\n---> \(eligBase.household.personDetails.count) \n")
Section("About You") {
ForEach($eligBase.household.personDetails) { $person in
HStack {
Text("Age")
Spacer()
Picker("", selection: $person.age) {
ForEach(0...100, id: \.self) {
Text("\($0)")
}
}.pickerStyle(.menu)
}
HStack {
Text("Marital Status")
Spacer()
Picker("", selection: $person.maritalStatus) {
ForEach(0...3, id: \.self) {
Text("\($0)")
}
}.pickerStyle(.menu)
}
}
}
}
.onAppear {
eligBase.household = Household(region: "xyz", householdSize: 0, receivingBenefits: [], energyCrisis: false, utilityProviders: [], residenceType: "", propertyTaxPastDue: false, homeNeedsRepairs: false, filedPreviousYearTaxReturn: false, heatingSystemNeedsRepairs: false, atRiskOfHomelessness: false, receivedMaximumBenefit: ReceivedMaximumBenefit(cip: false), personDetails: [PersonDetail(age: 33, maritalStatus: "single", minimumEmploymentOverExtendedPeriod: true, workStatus: "ok", pregnant: false, attendingSchool: false, disabled: false), PersonDetail(age: 44, maritalStatus: "maried", minimumEmploymentOverExtendedPeriod: true, workStatus: "ok", pregnant: false, attendingSchool: false, disabled: true)], incomes: [], assets: [])
}
}
You are trying to bind the control to the whole array of PersonDetail, not an individual entry within the array.
For example, if you always wanted to use the first personDetail instance in the array:
Toggle(isOn: $eligBase.household.personDetails.first!.disabled) {
Text("Disabled")
.font(.system(size: 15))
In a real solution you'd probably want to safely unwrap whatever PersonDetail instance you want in the array, and cleanly handle the array being empty (as the forced unwrap in my example would crash if the array was empty).
I have several criteria to filter array with. These criteria are optionals and stored in struct, because user can select only part of them. I have an array of models. I tried to use filter method, but you have to provide non optional criteria to it. What approach should be to get rid of optionals and add that criteria to filter method?
Filter struct with filtering options
struct Filter {
var brand: String?
var price: Int?
var consuption: Int?
}
Model class
class CarOffer {
var brand: String
var price: Int
var color: String
var consumption: Int
}
And here what I tried to do, but no luck because filter.price is optional and I don't know will it be or not. I understand that I have to remove an optional, but how to add a filter criteria in filter method depending on it's optionality? Or I am have choosen wrong approach?
let offers: [CarOffer] = […]
func applyFilter(filter: Filter) -> [CarOffer] {
let filteredOffers = offers.filter { $0.brand == filter.brand && $0.price <= filter.price && $0.consumption <= filter.consumption }
return filteredOffers
}
You would have an easier time by simplifying and breaking up your code into smaller pieces. There's no reason why a function to filter an array by some conditions, also has to be responsible for figuring out if an element meets those conditions. You've mentally trapped yourself thinking that the filter predicate has be one one long chain of && conditions in a closure.
struct CarOffer {
let brand: String
let price: Int
let color: String
let consumption: Int
}
struct CarFilter {
let brand: String?
let price: Int?
let consumption: Int?
func matches(car: CarOffer) -> Bool {
if let brand = self.brand, brand != car.brand { return false }
if let price = self.price, price != car.price { return false }
if let consumption = self.consumption, consumption != car.consumption { return false }
return true
}
}
extension Sequence where Element == CarOffer {
func filter(carFilter: CarFilter) -> [CarOffer] {
return self.filter(carFilter.matches)
}
}
let filter = CarFilter(brand: nil, price: nil, consumption: nil)
let offers: [CarOffer] = [] //...
let filteredOffers = offers.filter(carFilter: filter)
You can simply use a default value instead of filters Optional values. If you use the default value of the offer, filter will simply return true in case the optional properties were nil.
func applyFilter(filter: Filter) -> [CarOffer] {
let filteredOffers = offers.filter { $0.brand == filter.brand && $0.price <= (filter.price ?? $0.price) && $0.consumption <= (filter.consumption ?? $0.consumption) }
return filteredOffers
}
You could convert the filters to closures, and add an initializer that allows easy pass of filters that we do not care about:
struct Filter {
var brand: (String) -> Bool
var price: (Int) -> Bool
var consuption: (Int) -> Bool
init(brand: #escaping (String) -> Bool = { _ in return true },
price: #escaping (Int) -> Bool = { _ in return true },
consuption: #escaping (Int) -> Bool = { _ in return true }) {
self.brand = brand
self.price = price
self.consuption = consuption
}
}
This gives the best flexibility, as from this point on you can add any kind of filtering that you want. Like adding a filer based on your original structure, optionals for fields to ignore:
init(brand: String? = nil,
price: Int? = nil,
consuption: Int? = nil) {
self.brand = { brand == nil || brand == $0 }
self.price = { price == nil || price! <= $0 }
self.consuption = { consuption == nil || consuption! <= $0 }
}
My app in Xcode with swift language programming :
I have a struct like:
struct PageFilter {
var key: Int?
var title: NSString?
}
And then I have the values in:
filters are coming from API and i am saving them to extractedFilter
if let filters = filters {
for filter in filters {
var extractedFilter = PageFilter()
extractedFilter.key = filter["key"].integerValue
extractedFilter.title = filter["title"].stringValue
}
}
I have an array of page filter like :
lazy var availableFilters = Array<PageFilter>()
I want to fill the availableFilters with ExtractedFilter.
******* *i fixed the issue by a loop like this code :
var strFilter : String = ""
for var i = 0; i < self.newFilterList.availableGuildFilters.count; i++ {
let guildFilter = self.newFilterList.availableGuildFilters[i]
if guildFilter.selected {
strFilter += "\(guildFilter.key),"
}
}
thanks to all*
The following Swift 1.2 playground code would do it - I have put in a function to simulate the call to the API
//: Playground - noun: a place where people can play
import Cocoa
struct PageFilter {
var key: Int?
var title: NSString?
}
// this would be replaced by whatever way you get your filters from the API
func getFiltersFromApi() -> [PageFilter]? {
// return nil // uncomment this line to demo the API returning nothing
return [PageFilter(key: 1, title: "one"),
PageFilter(key: 2, title: "two"),
PageFilter(key: 3, title: "three"),
PageFilter(key: nil, title: nil)
]
}
let filters: [PageFilter]? = getFiltersFromApi() // API call, this could return nil
let extractedFilters: [PageFilter]
if let filters = filters {
extractedFilters = filters.map { filter in
PageFilter(key: filter.key, title: filter.title)
}
} else {
extractedFilters = []
}
for filter in extractedFilters {
println("key: \(filter.key), title: \(filter.title)")
}
Alternatively you could have your lazy var like this
var availableFilters: [PageFilter] = {
let filters: [PageFilter]? = getFiltersFromApi() // API call, this could return nil
if let filters = filters {
return filters.map { filter in
PageFilter(key: filter.key, title: filter.title)
}
} else {
return []
}
}()
The code is similar to Leonardo's answer, the main difference being the use of the map function instead of for ... in ...
Try like this:
struct PageFilter {
var key = Int()
var title = String()
}
var filters:[PageFilter]? = []
filters = [PageFilter(key: 1, title: "one"), PageFilter(key: 2, title: "two"), PageFilter(key: 3, title: "three")]
var extractedFilter = Array<PageFilter>()
if let filters = filters {
for filter in filters {
extractedFilter.append(PageFilter(key: filter.key, title: filter.title))
}
}
println(extractedFilter[1].key) // "2"
println(extractedFilter[1].title) // "two"
I fixed the issue by a loop like this:
var strFilter : String = ""
for var i = 0; i < self.newFilterList.availableGuildFilters.count; i++ {
let guildFilter = self.newFilterList.availableGuildFilters[i]
if guildFilter.selected {
strFilter += "\(guildFilter.key),"
}
}