1

I am very new to programming in Swift. So I'm trying to come up with a time management program. I have posted some code that have been derived from my project that is a work in progress, and I'm trying to troubleshoot some issues that I'm having that come from my lack of knowledge regarding Swift and SwiftUI. I would like to ask two questions here, but if you only have the answer to just one of them, I would greatly appreciate it.

So in my ContentView, I'm trying to display the taskName of the object with ID 0 using a Text in a VStack -- however, it is not displaying, and I'm not sure of the reason why. I can display the taskLength by putting it inside the String method, but taskName is not coming up when I attempt to display it.

Also I'm attempting to change the taskName of Task(id: 0) that is being passed into display2 directly from the display2, but I'm not sure if the taskName of Task(id: 0) is actually being changed, or it's only the taskName of @State var task:Task in display2 that is being changed -- based on my intuitions, I would think the latter case is actually happening. In that case, is there a way to directly edit the taskName of Task(id: 0) from display2?

import SwiftUI
import Foundation
import Combine

struct Task: Hashable, Codable, Identifiable {
    var id: Int
    var taskName: String = ""
    var taskLength: Int = 0
    var isBreak : Bool = false
}

class ModelData : ObservableObject{
    @Published var tasks: [Task] = [
        Task(id: 0,taskName: "Test", taskLength: 34, isBreak: false),
        Task(id: 1,taskName: "Math", taskLength: 30, isBreak: false),
        Task(id: 2,taskName: "Science", taskLength: 40, isBreak: false)
    ]
}

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData
    var body: some View {
        VStack{
            Text(Task(id: 0).taskName)
            display2(task:Task(id: 0))
        }
    }
}

struct display2: View{
    @State var task:Task
    var body: some View {
        TextField("New task",text: $task.taskName)
    }
}
swiftNewb
  • 27
  • 5

2 Answers2

1

The problem is here:

Text(Task(id: 0).taskName)

Here, you're creating a new Task, with an id of 0. This is not the first task inside your ModelData's tasks array.

Instead, reference the first task via subscript []:

Text(modelData.tasks[ /* index of task */ ].taskName)

Normally you can just put 0 here to get the first Task. However, you said you actually want the Task with an id of 0. You can do this via firstIndex(where:).

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData
    var body: some View {
        VStack{
            Text(
                modelData.tasks[getTaskIndexFrom(id: 0)] /// access 
                    .taskName
            )
            
            Display2( /// see https://stackoverflow.com/a/67064699/14351818
                task: $modelData.tasks[getTaskIndexFrom(id: 0)]
            )
        }
    }
    func getTaskIndexFrom(id: Int) -> Int {
        
        /// get first index of a task with the specified `id`
        if let firstIndex = modelData.tasks.firstIndex(where: { $0.id == 0 }) {
            return firstIndex
        } else {
            return 0
        }
    }
}

struct Display2: View{
    @Binding var task: Task /// need a Binding here
    var body: some View {
        TextField("New task", text: $task.taskName)
    }
}

Ok, your second question:

In that case, is there a way to directly edit the taskName of Task(id: 0) from display2?

Yep! Just use @Binding on Display2's task. This way, all changes will be synced back to your modelData.

aheze
  • 24,434
  • 8
  • 68
  • 125
  • Thanks for your reply -- it is very detailed. But I tried copying the code you provided, and I'm getting a fatal error. Thread 1: Fatal error: No ObservableObject of type ModelData found. A View.environmentObject(_:) for ModelData may be missing as an ancestor of this view. – swiftNewb Aug 23 '21 at 21:20
  • Make sure that inside `YourApp.swift`, you do `ContentView().environmentObject(ModelData())` or similar. This is also mentioned in [Taeeun Kim's answer](https://stackoverflow.com/a/68897972/14351818). – aheze Aug 23 '21 at 22:56
  • 1
    Yeah, I did that, and the problem still persists. It's fine; in my main program I implemented the syntax for accessing the first task that you gave me and used @Binding, and it worked out! So in the end, your solution worked for me -- thank you so much. – swiftNewb Aug 23 '21 at 23:32
  • @swiftNewb ah ok, nice! If it worked can you press the green checkmark to accept the answer? – aheze Aug 23 '21 at 23:43
0

In ContentView you used just Task(), but you have to use modelData for @Published var tasks in ModelData.




Task(id: 0).taskName -> modelData.tasks[1].taskName

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData
    
    var body: some View {
        VStack{
            Text(modelData.tasks[1].taskName)
                .foregroundColor(.blue)
            display2(task:Task(id: 0))
        }
    }
}



Also, as long as you use @EnvironmentObject, you need to add .environmentObject to the main as well.
(The code below is an example of the SwiftUI life cycle)

import SwiftUI

@main
struct ReplyToStackoverflowApp: App {
    var modelData: ModelData = ModelData()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(modelData)
        }
    }
}



enter image description here

Taeeun Kim
  • 964
  • 1
  • 8
  • 20