Cannot convert value of type '[ASValue<T>]' to expected argument type 'Binding<C>' - swift

I'm building a custom component and using ForEach with a custom generic type. The issue is that this type is throwing this error:
Cannot convert value of type '[ASValue]' to expected argument type 'Binding'
public struct ArrayStepperSection<T: Hashable>: Hashable {
public let header: String
public var items: [ASValue<T>]
public init(header: String = "", items: [ASValue<T>]) {
self.header = header
self.items = items
}
}
public struct ASValue<T: Hashable>: Hashable {
private let id = UUID()
var item: T
public init(item: T) {
self.item = item
}
}
public class ArrayStepperValues<T: Hashable>: Hashable, ObservableObject {
#Published public var values: [ASValue<T>]
#Published public var selected: ASValue<T>
#Published public var sections: [ArrayStepperSection<T>]
public init(values: [ASValue<T>], selected: ASValue<T>, sections: [ArrayStepperSection<T>]? = nil) {
self.values = values
self.selected = selected
if sections != nil {
self.sections = sections!
} else {
self.sections = [ArrayStepperSection(items: values)]
}
}
public static func == (lhs: ArrayStepperValues<T>, rhs: ArrayStepperValues<T>) -> Bool {
return lhs.sections == rhs.sections
}
public func hash(into hasher: inout Hasher) {
hasher.combine(sections)
}
}
struct ArrayStepperList<T: Hashable>: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var values: ArrayStepperValues<T>
let display: (T) -> String
var body: some View {
List {
ForEach(values.sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in // Error happens here
Button(action: {
values.selected.item = item
dismiss()
}) {
HStack {
Text(display(item))
Spacer()
if values.selected.item == item {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle(Text(display(values.selected.item)))
}
}

here is the test code I used to remove the error:
struct ContentView: View {
#StateObject var values = ArrayStepperValues(values: [ASValue(item: "aaa"),ASValue(item: "bbb")], selected: ASValue(item: "aaa"))
var body: some View {
ArrayStepperList(values: values) // for testing
}
}
struct ArrayStepperList<T: Hashable>: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var values: ArrayStepperValues<T>
// let display: (T) -> String // for testing
var body: some View {
List {
ForEach(values.sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in
Button(action: {
DispatchQueue.main.async {
values.selected.item = item.item // <-- here
// dismiss() // for testing
}
}) {
HStack {
Text("\(item.item as! String)") // for testing
Spacer()
if values.selected.item == item.item { // <-- here
Image(systemName: "checkmark")
}
}
}
}
}
}
}
.listStyle(InsetGroupedListStyle())
// .navigationTitle(Text(display(values.selected.item))) // for testing
}
}

Related

SwiftUI toggles not changing when clicked on

I'm trying to build a simple SwiftUI view that displays a number of toggles. While I can get everything to display ok, I cannot get the toggles to flip. Here's is a simplified code example:
import SwiftUI
class StoreableParam: Identifiable {
let name: String
var id: String { name }
#State var isEnabled: Bool
let toggleAction: ((Bool) -> Void)?
init(name: String, isEnabled: Bool, toggleAction: ((Bool) -> Void)? = nil) {
self.name = name
self.isEnabled = isEnabled
self.toggleAction = toggleAction
}
}
class StoreableParamViewModel: ObservableObject {
#Published var storeParams: [StoreableParam] = []
init() {
let storedParam = StoreableParam(name: "Stored Param", isEnabled: false) { value in
print("Value changed")
}
storeParams.append(storedParam)
}
}
public struct UBIStoreDebugView: View {
#StateObject var viewModel: StoreableParamViewModel
public var body: some View {
VStack {
List {
ForEach(viewModel.storeParams, id: \.id) { storeParam in
Toggle(storeParam.name, isOn: storeParam.$isEnabled)
.onChange(of: storeParam.isEnabled) {
storeParam.toggleAction?($0)
}
}
}
}.navigationBarTitle("Toggle Example")
}
}
As mentioned in the comments, there are a couple of things going on:
#State is only for use in a View
Your model should be a struct
Then, you can get a Binding using the ForEach element binding syntax:
struct StoreableParam: Identifiable {
let name: String
var id: String { name }
var isEnabled: Bool
let toggleAction: ((Bool) -> Void)?
}
class StoreableParamViewModel: ObservableObject {
#Published var storeParams: [StoreableParam] = []
init() {
let storedParam = StoreableParam(name: "Stored Param", isEnabled: false) { value in
print("Value changed")
}
storeParams.append(storedParam)
}
}
public struct UBIStoreDebugView: View {
#StateObject var viewModel: StoreableParamViewModel
public var body: some View {
VStack {
List {
ForEach($viewModel.storeParams, id: \.id) { $storeParam in
Toggle(storeParam.name, isOn: $storeParam.isEnabled)
.onChange(of: storeParam.isEnabled) {
storeParam.toggleAction?($0)
}
}
}
}
}
}

Animation not working when pass single object to the view in SwiftUI

I have a rather strange problem with animation on adding elements to list in SwiftUI. I have a simple model class:
struct ShoppingList: Identifiable, Equatable {
let id: UUID
var name: String
var icon: String
var items: [Item]
init(id: UUID = UUID(), name: String, icon: String, items: [Item] = []) {
self.id = id
self.name = name
self.icon = icon
self.items = items
}
static func == (lhs: ShoppingList, rhs: ShoppingList) -> Bool {
return lhs.id == rhs.id
}
}
extension ShoppingList {
struct Item: Identifiable, Equatable {
let id: UUID
var name: String
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
static func == (lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id
}
}
}
When I pass single ShoppingList object as binding to the view, adding animation basically doesn't happen at all:
Here is code of my view:
struct ShoppingListDetailView: View {
#Binding var shoppingList: ShoppingList
#State private var newItemName = ""
var body: some View {
List {
ForEach($shoppingList.items) { $item in
Text(item.name)
}
HStack {
TextField("Add item", text: $newItem)
Button(action: addNewItem) {
Image(systemName: "plus.circle.fill")
}
.disabled(newItemName.isEmpty)
}
}
.navigationTitle(shoppingList.name)
}
private func addNewItem() {
withAnimation {
let newItem = ShoppingList.Item(name: newItemName)
shoppingList.items.append(newItem)
}
}
}
And here is code of a parent view:
struct ShoppingListsView: View {
#Binding var shoppingLists: [ShoppingList]
var body: some View {
List {
ForEach($shoppingLists) { $list in
NavigationLink(destination: ShoppingListDetailView(shoppingList: $list)) {
ShoppingListItemView(shoppingList: $list)
}
}
}
.navigationTitle("Shopping List")
}
}
But once I pass whole list of ShoppingList objects and index for a particular one, everything works as expected:
Code with passing list to the view looks like that:
struct ShoppingListDetailView: View {
#Binding var shoppingList: [ShoppingList]
var index: Int
#State private var newItemName = ""
var body: some View {
List {
ForEach($shoppingList[index].items) { $item in
Text(item.name)
}
HStack {
TextField("Add item", text: $newItem)
Button(action: addNewItem) {
Image(systemName: "plus.circle.fill")
}
.disabled(newItemName.isEmpty)
}
}
.navigationTitle(shoppingList[index].name)
}
private func addNewItem() {
withAnimation {
let newItem = ShoppingList.Item(name: newItemName)
shoppingList[index].items.append(newItem)
}
}
}
And of course parent view:
struct ShoppingListsView: View {
#Binding var shoppingLists: [ShoppingList]
var body: some View {
List {
ForEach($shoppingLists) { $list in
NavigationLink(destination: ShoppingListDetailView(shoppingList: $shoppingLists, index: shoppingLists.firstIndex(of: list)!)) {
ShoppingListItemView(shoppingList: $list)
}
}
}
.navigationTitle("Shopping List")
}
}
I'm new to Swift (not to mention SwiftUI) and I have no idea what might be wrong here. Any ideas?

I want to create a loop that creates 10 different strings and use that strings to show 10 different cards

import Foundation
class Card: Identifiable, ObservableObject {
var id = UUID()
var result: String = ""
init() {
for _ in 1...10 {
let suit = ["D","K","B","S"][Int(arc4random() % 4]
let numStr = ["1","2","3","4"][Int(arc4random() % 4]
result = suit + numStr
}
}
}
import SwiftUI
struct EasyModeView: View {
#ObservedObject var cardModel: Card
var body : some View {
HStack {
ForEach((1...10, id: \.self) { _ in
Button { } label: { Image(cardModel.result) } } }
}
}
I created loop but it always shows me 10 same cards and i want all 10 different.
My Images in Assets are named same as combination of two variables example "D1","D2","S1","S2" etc.
Here is a right way of what you are trying:
struct ContentView: View {
var body: some View {
EasyModeView()
}
}
struct EasyModeView: View {
#StateObject var cardModel: Card = Card()
var body : some View {
HStack {
ForEach(cardModel.results) { card in
Button(action: {}, label: {
Text(card.result)
.padding(3.0)
.background(Color.yellow.cornerRadius(3.0))
})
}
}
Button("re-set") { cardModel.reset() }.padding()
}
}
struct CardType: Identifiable {
let id: UUID = UUID()
var result: String
}
class Card: ObservableObject {
#Published var results: [CardType] = [CardType]()
private let suit: [String] = ["D","K","B","S"]
init() { initializing() }
private func initializing() {
if (results.count > 0) { results.removeAll() }
for _ in 0...9 { results.append(CardType(result: suit.randomElement()! + String(describing: Int.random(in: 1...4)))) }
}
func reset() { initializing() }
}
Result:
You are using only one card, instead of creating 10. Fix like this
struct EasyModeView: View {
var body : some View {
HStack {
ForEach(0..<10) { _ in
Button { } label: {
Image(Card().result)
}
}
}
}
}

TextField in SwiftUI loses focus when I enter a character

I have a problem when I enter a character within a TextField (Within ExerciseSetView), I have to re-click the text box to make it so I can enter another character. If I remove the bindings from the Textfield I can enter text fluidly.
I think it has something to do with my presenter class and the updateSet function recreating a set instance because I have to replace some values two levels deep within an array.
//
// ContentView.swift
// test
//
//
import SwiftUI
import Combine
import CoreData
class WorkoutExerciseSetVM: Hashable, ObservableObject {
#Published public var id: Int
#Published public var reps: String
#Published public var weight: String
init(id: Int, reps: String, weight: String) {
self.id = id
self.reps = reps
self.weight = weight
}
static func ==(lhs: WorkoutExerciseSetVM, rhs: WorkoutExerciseSetVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutExerciseVM: Hashable, ObservableObject {
#Published public var id: UUID
#Published public var name: String
#Published public var sets: [WorkoutExerciseSetVM]
init(id: UUID, name: String, sets: [WorkoutExerciseSetVM]) {
self.id = id
self.name = name
self.sets = sets
}
static func ==(lhs: WorkoutExerciseVM, rhs: WorkoutExerciseVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutVM: Hashable, ObservableObject {
#Published public var id = UUID()
#Published public var name: String
#Published public var exercises: [WorkoutExerciseVM]
#Published public var started: Date? = Date()
#Published public var completed: Date? = Date()
init(id: UUID, name: String, exercises: [WorkoutExerciseVM], started: Date?, completed: Date?) {
self.id = id
self.name = name
self.exercises = exercises
self.started = started
self.completed = completed
}
static func ==(lhs: WorkoutVM, rhs: WorkoutVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutPresenter: ObservableObject {
#Published public var id: UUID
#Published public var exercises: [WorkoutExerciseVM]
#Published public var name: String
#Published public var started: Date?
#Published public var completed: Date?
init(routine: WorkoutVM) {
self.id = UUID()
self.name = routine.name
self.started = Date()
self.completed = nil
self.exercises = routine.exercises.map{ exercise in
return WorkoutExerciseVM(
id: UUID(),
name: exercise.name,
sets: [
WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0")
]
)
}
}
func removeExercise(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.filter{$0.id.uuidString != exerciseId}
}
func addSet(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.map {
if ($0.id.uuidString == exerciseId) {
if ($0.sets.count == 0) {
$0.sets.append(WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0"))
}
if let lastSet = $0.sets.last {
$0.sets.append(WorkoutExerciseSetVM(id: lastSet.id + 1, reps: lastSet.reps, weight: lastSet.weight))
}
}
return $0
}
}
func updateSet(id: UUID, set: WorkoutExerciseSetVM) {
let exerciseId = id.uuidString
self.exercises = self.exercises.map{
if $0.id.uuidString == exerciseId {
$0.sets = $0.sets.map{(oldExerciseSet) -> WorkoutExerciseSetVM in
if oldExerciseSet.id == set.id {
return set
}
return oldExerciseSet
}
return $0
}
return $0;
}
}
func removeSet(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.map{(exercise) -> WorkoutExerciseVM in
if exercise.id.uuidString == exerciseId {
let newExercise = exercise
if newExercise.sets.count > 1 {
newExercise.sets.removeLast()
}
return newExercise
}
return exercise;
}
}
}
struct ContentView: View {
var body: some View {
VStack {
WorkoutView(presenter: WorkoutPresenter(routine: WorkoutVM(id: UUID(), name: "Test", exercises: [WorkoutExerciseVM(id: UUID(), name: "Exercise", sets: [WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0")])], started: nil, completed: nil)))
}
}
}
struct WorkoutView: View {
#ObservedObject var presenter: WorkoutPresenter
var body: some View {
return GeometryReader { geo in
ZStack {
VStack {
ScrollView {
ForEach(self.presenter.exercises, id: \.self) { exercise in
ExerciseView(presenter: self.presenter, exercise: exercise)
}
}
}
}
}
}
}
struct ExerciseView: View {
#ObservedObject var presenter: WorkoutPresenter
var exercise: WorkoutExerciseVM
var body: some View {
VStack {
VStack(alignment: .leading) {
VStack {
VStack {
ForEach(exercise.sets, id: \.self) { exerciseSet in
ExerciseSetView(
set: exerciseSet,
onUpdate: { newExerciseSet in
self.presenter.updateSet(id: self.exercise.id, set: newExerciseSet)
}
)
}
}
}
}
HStack {
Button(action: {
self.presenter.addSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "plus")
Text("Add Set")
}
}
Button(action: {
self.presenter.removeSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "minus")
Text("Remove Set")
}
}
}
}
}
}
struct ExerciseSetView: View {
var set: WorkoutExerciseSetVM
var onUpdate: (_ set: WorkoutExerciseSetVM) -> Void
var body: some View {
let repBinding = Binding(
get: {
String(self.set.reps)
},
set: {
if ($0 as String?) != nil {
self.onUpdate(WorkoutExerciseSetVM(id: self.set.id, reps: $0 , weight: self.set.weight))
}
}
)
let weightBinding = Binding(
get: {
String(self.set.weight)
},
set: {
if ($0 as String?) != nil {
self.onUpdate(WorkoutExerciseSetVM(id: self.set.id, reps: self.set.reps, weight: $0 ))
}
}
)
return HStack {
Spacer()
// textfield that isn't working
TextField("", text: repBinding)
Spacer()
// textfield that isn't working
TextField("", text: weightBinding)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Your code has some fundamental errors. Please research about ObservableObject and Published values before going into production with this code. Otherwise, it would be quite hard to deal with this code later.
I have updated your views and that seems to work. You are not using ObservableObject as they should be used. Just pass ObservableObjectss around let them do the bindings for you instead of setting custom bindings.
struct ExerciseView: View {
#ObservedObject var presenter: WorkoutPresenter
#ObservedObject var exercise: WorkoutExerciseVM
var body: some View {
VStack {
VStack(alignment: .leading) {
ForEach(exercise.sets, id: \.self) { exerciseSet in
ExerciseSetView(set: exerciseSet)
}
}
HStack {
Button(action: {
self.presenter.addSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "plus")
Text("Add Set")
}
}
Button(action: {
self.presenter.removeSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "minus")
Text("Remove Set")
}
}
}
}
}
}
struct ExerciseSetView: View {
#ObservedObject var set: WorkoutExerciseSetVM
var body: some View {
HStack {
Spacer()
TextField("", text: $set.reps)
Spacer()
TextField("", text: $set.weight)
}
}
}
Let me know if this works for you.

Update a row in a list (SwiftUI)

I'm an early bird in programming so I know this question can be ridiculous from the point of view of an expert but I'm stuck in this situation from several days.
I would like to update a row by using a button "Edit" (pencil) after having used another button to store the item with a TextField.
Here's the code:
class Food: Hashable, Codable, Equatable {
var id : UUID = UUID()
var name : String
init(name: String) {
self.name = name
}
static func == (lhs: Food, rhs: Food) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
class Manager: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#Published var shoppingChart: [Food] = []
init() {
let milk = Food(name: "Milk")
let coffee = Food(name: "Coffee")
shoppingChart.append(milk)
shoppingChart.append(coffee)
}
func newFood(name: String) {
let food = Food(name: name)
shoppingChart.insert(food, at: 0)
}
}
struct ContentView: View {
#ObservedObject var dm : Manager
#State var isAddFoodOpened = false
var body: some View {
VStack {
List {
ForEach(self.dm.shoppingChart, id:\.self) { food in
HStack {
Text(food.name)
Image(systemName: "pencil")
}
}
}
self.buttonAdd
}
}
var buttonAdd: some View {
Button(action: {
self.isAddFoodOpened.toggle()
}) {
Text("Add")
}
.sheet(isPresented: $isAddFoodOpened) {
Add(dm: self.dm, fieldtext: "", isAddFoodOpened: self.$isAddFoodOpened)
}
}
}
struct Add: View {
#ObservedObject var dm : Manager
#State var fieldtext : String = ""
#Binding var isAddFoodOpened : Bool
var body: some View {
VStack {
TextField("Write a food", text: $fieldtext)
buttonSave
}
}
var buttonSave : some View {
Button(action: {
self.dm.newFood(name: self.fieldtext)
self.isAddFoodOpened = false
}) {
Text("Save")
}
}
}
The #ObservedObject var dm : Manager object is never initialized.
Try initialized dm in ContentView like this:
#ObservedObject var dm = Manager()
Ok, so if I understand correctly you want to update/edit a row by using a button "Edit".
This will do it:
struct ContentView: View {
#ObservedObject var dm : Manager
#State var isAddFoodOpened = false
#State var isEditOpened = false
#State var fieldtext : String = ""
var body: some View {
VStack {
List {
ForEach(0..<self.dm.shoppingChart.count, id:\.self) { i in
HStack {
Text(self.dm.shoppingChart[i].name)
Button(action: { self.isEditOpened.toggle() }) {
Image(systemName: "pencil")
}.sheet(isPresented: self.$isEditOpened) {
TextField(self.dm.shoppingChart[i].name, text: self.$fieldtext, onEditingChanged: { _ in
self.dm.shoppingChart[i].name = self.fieldtext
})
}
}
}
}
self.buttonAdd
}
}
var buttonAdd: some View {
Button(action: {
self.isAddFoodOpened.toggle()
}) {
Text("Add")
}
.sheet(isPresented: $isAddFoodOpened) {
Add(dm: self.dm, fieldtext: "", isAddFoodOpened: self.$isAddFoodOpened)
}
}
}