In the following code I have a struct that has different values.
struct Workout: Codable {
let exercise: [Exercise]
enum CodingKeys: String, CodingKey {
case exercise = "exercise"
}
}
struct Exercise: Codable {
let exercise, set: Int
}
class WorkoutViewModel: ObservableObject {
#Published var workout: Workout
init(_ workout: Workout) {
self.workout = workout
}
}
In my main view I call initialise the value as this:
struct CreateWorkoutView: View {
#ObservedObject var myExercises = WorkoutViewModel(Workout(exercise: [Exercise(exercise: 1, set: 1)]))
var body: some View {
NavigationView {
NavigationLink(
destination: View2(exerciseList: self.$myExercises.workout.exercise)
){
Text("Add Exercises")
}
}
struct View2: View {
#ObservedObject var excersices = getExercisesData()
#Binding var exerciseList: [Exercise]
var body: some View {
VStack {
List(excersices.datas){i in
HStack{
VStack(alignment: .leading){
Text(i.name)
}
Spacer()
VStack(alignment: .leading){
Image(systemName: "info.circle")
}
}.onTapGesture {
self.exerciseList.append(Exercise(exercise: i.exerciseId, set: 1))
}
}
}
}
}
There is a problem with the line where I set self.$myExercises.workout.exercise. The XCode error shows a different location (a HStack) and gives the following error as it seems unrelated:
Generic parameter 'C0' could not be inferred Info: 1. In call to function 'buildBlock' (SwiftUI.ViewBuilder)
I want to move this the exercise array to the next view to add items to it.
any idea why the above part of the cone is not allowed?
Thanks
The error message was unrelated. The problem was that the let exercise, set: Int was a constant and not a var. changing that solved the issue.
Commenting the part the error raised sometimes help to understand what the compiler is really complaining about.
Related
I am making a Slider based on a view model, but I am facing this error message Initializer 'init(value:in:step:label:minimumValueLabel:maximumValueLabel:onEditingChanged:)' requires that 'Int.Stride' (aka 'Int') conform to 'BinaryFloatingPoint'
It is strange because converting the integer from view model into Double doesn't quite do the trick.
I found very similar question and read the SO answer (How can I make Int conform to BinaryFloatingPoint or Double/CGFloat conform to BinaryInteger?), but it doesn't seem like I can implementation the solution for my case, probably because I am using ObservedObject for the view model.
If I remove $ in front of setInformationVM.elapsedRestTime, I would see another error message saying Cannot convert value of type 'Int' to expected argument type 'Binding<Int>'
They said "Binding are generally used when there is a need for 2-way communication" - would that mean the Slider needs a way to communicate/update back to the View Model? Why is it that the Slider was accepting #State private var xx: Double for the value in general , but not a simple integer from my view model?
import Foundation
import SwiftUI
import Combine
struct SetRestDetailView: View {
#EnvironmentObject var watchDayProgramVM: WatchDayProgramViewModel
#State var showingLog = false
var body: some View {
GeometryReader { geometry in
ZStack() {
(view content removed for readability)
}
.sheet(isPresented: $showingLog) {
let setInformatationVM = self.watchDayProgramVM.exerciseVMList[0].sets[2]
setLoggingView(setInformationVM: setInformatationVM, restfullness: 3, stepValue: 10)
}
}
}
setLoggingView
struct setLoggingView: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var setInformationVM: SetInformationTestClass
#State var restfullness: Int
var stepValue: Int
var body: some View {
GeometryReader { geometry in
let rect = geometry.frame(in: .global)
ScrollView {
VStack(spacing: 5) {
Text("Rested \(Int(setInformationVM.elapsedRestTime)) sec")
Slider(value: $setInformationVM.elapsedRestTime,
in: 0...setInformationVM.totalRestTime,
step: Int.Stride(stepValue),
label: {
Text("Slider")
}, minimumValueLabel: {
Text("-\(stepValue)")
}, maximumValueLabel: {
Text("+\(stepValue)")
})
.tint(Color.white)
.padding(.bottom)
Divider()
Spacer()
Text("Restfullness")
.frame(minWidth: 0, maxWidth: .infinity)
restfullnessStepper(rect: rect, maxRestFullness: 5, minRestFullness: 1, restfullnessIndex: restfullness)
Button(action: {
print("Update Button Pressed")
//TODO
//perform further actions to update restfullness metric and elapsed rest time in the viewmodels before dismissing the view, and also update the iOS app by synching the view model.
dismiss()
}) {
HStack {
Text("Update")
.fontWeight(.medium)
}
}
.cornerRadius(40)
}
.border(Color.yellow)
}
}
}
SetInformationTestClass view model
class SetInformationTestClass: ObservableObject {
init(totalRestTime: Int, elapsedRestTime: Int, remainingRestTime: Int, isTimerRunning: Bool) {
self.totalRestTime = totalRestTime
self.elapsedRestTime = elapsedRestTime
self.remainingRestTime = remainingRestTime
}
#Published var totalRestTime: Int
#Published var elapsedRestTime: Int
#Published var remainingRestTime: Int
You can create a custom binding variable like :
let elapsedTime = Binding(
get: { Double(self.setInformationVM.elapsedRestTime) },
set: { self.setInformationVM.elapsedRestTime = Int($0) } // Or other custom logic
)
// then you reference it in the slider like:
Slider(elapsedTime, ...)
I'm attempting to call a function in a struct (swiftui view) which appends an item to an array that is then mapped to a list. The function is being called in a subview, but I keep getting the error "Cannot use mutating member on immutable value: 'self' is immutable".
Heres the function in the parent:
mutating func addNote(note: String){
var newNotes = notes;
newNotes.append(note);
notes = newNotes;
}
Inside the body has:
List {
ForEach(notes, id: \.self) { string in
Section(header: Text("1/22/20")){
Text(string)
}
}...
To pass the function to the subview i try this:
NavigationLink(destination: AddNoteView(delegate: addNote)) {
Text("Add note")
}
and my addNoteView() looks like this:
struct AddNoteView : View {
#State var note: String = ""
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var delegate: (String) -> ()
var body: some View {
NavigationView{
Form{
Section(header: Text("Note")){
TextField("Note", text:$note)
}
Section(){
Button(action: {
delegate(note)
presentationMode.wrappedValue.dismiss()}){
Text("Add note")
}
Anyone having any clue what I'm doing wrong?
SwiftUI view is struct you cannot mutate it from within self. Make notes as #State then you can use
#State var notes: [String] = []
// .. other code
func addNote(note: String) {
notes.append(note)
}
I am trying to create a list using ForEach and NavigationLink of an array of data.
I believe my code (see the end of the post) is correct but my build fails due to
"Missing argument for parameter 'index' in call" and takes me to SceneDelegate.swift a place I haven't had to venture before.
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
I can get the code to run if I amend to;
let contentView = ContentView(habits: HabitsList(), index: 1)
but then all my links hold the same data, which makes sense since I am naming the index position.
I have tried, index: self.index (which is what I am using in my NavigationLink) and get a different error message - Cannot convert value of type '(Any) -> Int' to expected argument type 'Int'
Below are snippets of my code for reference;
struct HabitItem: Identifiable, Codable {
let id = UUID()
let name: String
let description: String
let amount: Int
}
class HabitsList: ObservableObject {
#Published var items = [HabitItem]()
}
struct ContentView: View {
#ObservedObject var habits = HabitsList()
#State private var showingAddHabit = false
var index: Int
var body: some View {
NavigationView {
List {
ForEach(habits.items) { item in
NavigationLink(destination: HabitDetail(habits: self.habits, index: self.index)) {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.description)
}
}
}
}
}
}
}
}
struct HabitDetail: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var habits: HabitsList
var index: Int
var body: some View {
NavigationView {
Form {
Text(self.habits.items[index].name)
}
}
}
}
You probably don't need to pass the whole ObservedObject to the HabitDetail.
Passing just a HabitItem should be enough:
struct HabitDetail: View {
#Environment(\.presentationMode) var presentationMode
let item: HabitItem
var body: some View {
// remove `NavigationView` form the detail view
Form {
Text(item.name)
}
}
}
Then you can modify your ContentView:
struct ContentView: View {
#ObservedObject var habits = HabitsList()
#State private var showingAddHabit = false
var body: some View {
NavigationView {
List {
// for every item in habits create a `linkView`
ForEach(habits.items, id:\.id) { item in
self.linkView(item: item)
}
}
}
}
// extract to another function for clarity
func linkView(item: HabitItem) -> some View {
// pass just a `HabitItem` to the `HabitDetail`
NavigationLink(destination: HabitDetail(item: item)) {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.description)
}
}
}
}
}
I have a struct of that is a part of the data set as below:
var exercise: [Exercise]
struct Exercise: Codable {
var id = UUID()
var exercise, set: Int
}
In my code (View1) I loop through the exercise array and reach each element like:
ForEach(exercise, id: \.id){ item in
NavigationLink( destination: NextView(arrayItem: item)){
Text("\(item.exercise)")
}
}
So the item I will send to NextView must be something like Exercise(id: E21, exercise: 227, set: 1)
In the NextView I do not know how to define this binding variable. I tried this:
struct NextView: View {
#Binding var arrayItem: Exercise
}
The above variable (arrayItem) does not match with the item I set in the View1.
Any idea what arrayItem must be like to accept the item value?
This is the error I get:
'NextView.Type' is not convertible to '(Binding<Exercise>, Binding<String>, Int) -> NextView'
One of the solutions can be to use #ObservableObject and make your exercises list #Published.
First you need some class to store your exercises:
class ViewModel: ObservableObject {
#Published var exercises = [
Exercise(exercise: 227, set: 1),
Exercise(exercise: 13, set: 2),
Exercise(exercise: 423, set: 2)
]
}
In the NavigationLink you pass the binding of your chosen item - you can do this because you observe the ViewModel now.
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
List {
ForEach(viewModel.exercises, id: \.id) { item in
NavigationLink(destination: NextView(exercise: self.$viewModel.exercises[self.viewModel.exercises.firstIndex(of: item)!])) {
Text("\(item.exercise)")
}
}
}
}
}
}
In the NextView you can use your #Binding variable as needed (I tried modifying the exercise.exercise value:
struct NextView: View {
#Binding var exercise: Exercise
var body: some View {
Text("Exercise: \(exercise.exercise)")
.onTapGesture {
self.exercise.exercise += 1
}
}
}
Lastly your model needs to conform to Equatable - you can't use firstIndex(of:) without it:
struct Exercise: Codable, Equatable {
var id = UUID()
var exercise, set: Int
}
I have a class DataPoint which is the value of a dictionary.
DataPoint has a member variable value that I need to bind to a Slider in SwiftUI.
I provide the data class AppData as #Environment Object to SwiftUI, the dictionary holds the instances of class DataPoint.
I fail to manage to bind the DataPoint.value member variable to the SwiftUI Slider value.
These are the relevant code snippets.
The #Published data:
class AppData: ObservableObject {
#Published var dataPoints: [UUID : DataPoint] = [:] {
...
}
The data structure:
class DataPoint: Identifiable {
var id = UUID()
var value: Double
}
The SwiftUI view of DataPoints AppDataList:
struct AppDataList: View {
#EnvironmentObject var appData: AppData
var body: some View {
NavigationView {
List(Array(appData.dataPoints.values)) { dataPoint in
NavigationLink(destination: DataPointDetail(dataPointID: dataPoint.id)) {
Text("\(dataPoint.value)")
}
}
...
}
The SwiftUI DataPointDetail view that I struggle with, it is referenced from AppDataList:
struct DataPointDetail: View {
#EnvironmentObject var appData: AppData
var dataPointID: UUID
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text("Data Point Detail")
Text("\(appData.dataPoints[dataPointID]!.value)")
/* This line */
/* troubles */
/* me! */
/* ---> */ Slider(value: appData.dataPoints[dataPointID]?.value, in: 0...10000)
Spacer()
Text("\(appData.dataPoints[dataPointID]!.id)")
}
}
}
}
The root content view:
struct ContentView: View {
#EnvironmentObject var appData: AppData
var body: some View {
VStack {
if appData.dataPoints.count > 0 {
AppDataList()
} else {
NoDataPoints()
}
}
}
}
The creation of the root content view in SceneDelegate:
let contentView = ContentView()
.environmentObject(appData)
I get an error in the editor: Static member 'leading' cannot be used on instance of type 'HorizontalAlignment' and it is in the line of VStack in DataPointDetail view. I believe that it has got nothing to do with the VStack.
Commenting out the Slider line produces compilable and runnable code.
How would one accomplish this?
Most quick solution is to use wrapper binding, as in below snapshot
Slider(value: Binding<Double>(
get: { self.appData.dataPoints[self.dataPointID]?.value ?? 0 },
set: { self.appData.dataPoints[self.dataPointID]?.value = $0}), in: 0...10000)