Is it possible to remove an item from an array within a struct swiftUI?

Viewed 87

I'm learning SwiftUI and am still relatively new at it. To learn, I've been slowly building a "to-do list" style app, as it seemed relatively easy to implement for someone starting out. I probably asked the question wrong, but I'm trying to set it up so that my custom type Project has an array that stores another custom type Task, such that a project in the app can have tasks added and removed from it. I'm struggling to figure out how to remove items from the project's task list, though.

Here's the Project and Task models so far.

Project:

import Foundation
import SwiftUI

struct Project: Identifiable, Codable {

    let id: UUID
    var projectTitle: String
    var projectNotes: String
    var projectCategory: Category
    var taskList: [Task]

    init(id:UUID = UUID(), projectTitle:String, projectNotes:String = "", projectCategory:Category, taskList:[Task]) {

        self.id = id
        self.projectTitle = projectTitle
        self.projectNotes = projectNotes
        self.projectCategory = projectCategory
        self.taskList = taskList
    }
}

extension Project {
    struct Data {
        var projectTitle:String = ""
        var projectNotes:String = ""
        var projectCategory: Category = .personal
        var taskList: [Task]
    }

    var data: Data {
        Data(projectTitle: projectTitle, projectNotes: projectNotes, projectCategory: projectCategory, taskList: taskList)
    }
}

extension Project {
    static let sampleData: [Project] =
    [
        Project(projectTitle: "Mindfulness", projectCategory: .personal ,taskList: Task.sampleData),
        Project(projectTitle: "Clean House", projectCategory: .home, taskList: Task.sampleData),
        Project(projectTitle: "Finish Audit", projectCategory: .work, taskList: Task.sampleData)
    ]

}

Task:

import Foundation
import SwiftUI

struct Task: Identifiable, Hashable, Codable {
    let id: UUID
    var taskTitle: String
    var taskNotes: String
    var isTapped: Bool

    init(id:UUID = UUID(), taskTitle:String, taskNotes:String = "", isTapped:Bool = false) {
        self.id = id
        self.taskTitle = taskTitle
        self.taskNotes = taskNotes
        self.isTapped = isTapped
    }
}

extension Task {
    struct Data {
        var taskTitle:String = ""
        var taskNotes:String = ""
        var isTapped:Bool = false
    }

    var data: Data {
        Data(taskTitle: taskTitle, taskNotes: taskNotes, isTapped: isTapped)
    }
}

extension Task {
    static let sampleData: [Task] =
    [
        Task(taskTitle: "Mow Lawn", taskNotes: "Make sure you set height to 4"),
        Task(taskTitle: "Drop off suit and blazer", taskNotes: "Has to be done this week"),
        Task(taskTitle: "Call Tree Trimmer", taskNotes: "Get another quote if needed")
    ]
}

HighestPriorityView updates in simulator when clicking the button to remove the task from the firstPersonalProject (or other categories), and the next task pops up. The problem is, when I go back to that project in another view, the task is still there... I can confirm this is because when I update the scenePhase in ContentView, all of the tasks immediately come back as if not removed from the array. Because it visually updates when clicking the button, but then resets to what was passed into the view when the scenePhase changes, I'm left to wonder if somehow I'm working with a copy of firstPersonalProject within the HighestPriorityView, versus binding to the original version of that project passed into the view? Is it even possible to remove a Task from a bound Project's task list like I'm trying to do?

Hopefully this makes sense and any advice at all is appreciated.

Here's the ContentView.

import SwiftUI

struct ContentView: View {
    @Binding var taskList: [Task]
    @Binding var firstPersonalProject: Project
    @Binding var firstHomeProject: Project
    @Binding var firstWorkProject: Project
    @Binding var projectList: [Project]
    
    @State private var highestPriority: Task = Task(taskTitle: "")
    @State private var category: Category = .personal
    @State private var confirmationShown = false
    @State private var projectToComplete: Project = Project(projectTitle: "", projectCategory: .personal, taskList: [])
    
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some View {
        List {
            Section("What Should I Work On Now?") {
                switch category {
                case .personal: HighestPriorityView(completeTask: {
                    let projectCount = $firstPersonalProject.taskList.count
                    switch projectCount>1 {
                    case true: firstPersonalProject.taskList.remove(at: 0)
                    case false: completeProject(project: firstPersonalProject)
    
                    }
                }, task: firstPersonalProject.taskList[0])
                    
                    
                case .home: HighestPriorityView(completeTask: {
                    let projectCount = firstHomeProject.taskList.count
                    switch projectCount>1 {
                    case true: firstHomeProject.taskList.remove(at: 0)
                    case false: completeProject(project: firstHomeProject)
                    }
                    
                }, task: firstHomeProject.taskList[0])
                        

                case .work: HighestPriorityView(completeTask: {
                    let projectCount = firstWorkProject.taskList.count
                    switch projectCount>1 {
                    case true: firstWorkProject.taskList.remove(at: 0)
                    case false: completeProject(project: firstWorkProject)
                    }
                    
                }, task: firstWorkProject.taskList[0])
                }
            }
            Section("Category") {
                Picker(selection: $category, label: /*@START_MENU_TOKEN@*/Text("Picker")/*@END_MENU_TOKEN@*/) {
                    ForEach(Category.allCases) { category in
                        Text(category.rawValue.uppercased()).tag(category)
                    }
                }
                .pickerStyle(.segmented)
            }
            Section("Add New Tasks") {
                newTaskView(taskList: $taskList)
            }
        }
        
        .onChange(of: scenePhase) { newScenePhase in
            firstPersonalProject = projectList.first(where: {$0.projectCategory == .personal}) ?? Project(projectTitle: "No Personal Projects", projectCategory: .personal, taskList: [Task(taskTitle: "Do the process!")])
            firstHomeProject = projectList.first(where: {$0.projectCategory == .home}) ?? Project(projectTitle: "No Home Projects", projectCategory: .home, taskList: [Task(taskTitle: "Do the process!")])
            firstWorkProject = projectList.first(where: {$0.projectCategory == .work}) ?? Project(projectTitle: "No Work Projects", projectCategory: .work, taskList: [Task(taskTitle: "Do the process!")])
        }
        
        .confirmationDialog("Complete project?", isPresented: $confirmationShown) {
            Button("Yes") {
                withAnimation {
                    projectToComplete.taskList.remove(at: 0)
                    if let index = projectList.firstIndex(where: {$0.projectCategory == projectToComplete.projectCategory}) {
                        projectList.remove(at: index)
                    }
                    firstPersonalProject = projectList.first(where: {$0.projectCategory == .personal}) ?? Project(projectTitle: "No Personal Projects", projectCategory: .personal, taskList: [Task(taskTitle: "Do the process!")])
                    firstHomeProject = projectList.first(where: {$0.projectCategory == .home}) ?? Project(projectTitle: "No Home Projects", projectCategory: .home, taskList: [Task(taskTitle: "Do the process!")])
                    firstWorkProject = projectList.first(where: {$0.projectCategory == .work}) ?? Project(projectTitle: "No Work Projects", projectCategory: .work, taskList: [Task(taskTitle: "Do the process!")])
                    projectToComplete = Project(projectTitle: "", projectCategory: .personal, taskList: [])
                }
            }
        } message: {
            Text("Complete project?")
        }
    }
    
    func completeProject(project: Project) {
        projectToComplete = project
        confirmationShown = true
    }


}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(taskList: .constant(Task.sampleData), firstPersonalProject: .constant(Project.sampleData[0]), firstHomeProject: .constant(Project.sampleData[1]), firstWorkProject: .constant(Project.sampleData[2]), projectList: .constant(Project.sampleData))
    }
}

HighestPriorityView:

import SwiftUI

struct HighestPriorityView: View {
    var completeTask: () -> Void
    let task: Task
    
    var body: some View {
        HStack {
            VStack (alignment: .leading) {
                Text("\(task.taskTitle)".uppercased())
                        .font(.headline)
                Text("\(task.taskNotes)")
                        .font(.caption)
            }
            Spacer()
            Button(action: completeTask) {
                Image(systemName: "star.fill")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 50, height: 50, alignment: .center)
                    .foregroundColor(.yellow)
            }
        }
        .padding(5)
    }
}

struct HighestPriorityView_Previews: PreviewProvider {
    static var task: Task = Task.sampleData[0]
    static var previews: some View {
        HighestPriorityView(completeTask: {}, task: task)
            .previewLayout(.sizeThatFits)
    }
}

Here's MainView() that passes data into ContentView()

import SwiftUI

struct MainView: View {
    @State private var selectedView: Int = 1
    @StateObject private var taskList = FileSaveLoad()
    @StateObject private var projectList = FileSaveLoad()
    @State var firstPersonalProject: Project = Project(projectTitle: "", projectCategory: .personal, taskList: [MyTask(taskTitle: "Do the process!")])
    @State var firstHomeProject: Project = Project(projectTitle: "", projectCategory: .home, taskList: [MyTask(taskTitle: "Do the process!")])
    @State var firstWorkProject: Project = Project(projectTitle: "", projectCategory: .work, taskList: [MyTask(taskTitle: "Do the process!")])
    @Environment(\.scenePhase) private var scenePhase

    
    var body: some View {
        
        TabView(selection: $selectedView) {
            NavigationView {
                ContentView(taskList: $taskList.tasks, firstPersonalProject: $firstPersonalProject, firstHomeProject: $firstHomeProject, firstWorkProject: $firstWorkProject, projectList: $projectList.projects)
                    .navigationTitle("You're Focused!")
                    
            }
            .tabItem { Label("Focus Mode", systemImage: "star.fill")
            }
            .tag(1)
            
            
            NavigationView {
                ProcessView(taskList: $taskList.tasks, projectList: $projectList.projects, firstPersonalProject: $firstPersonalProject, firstHomeProject: $firstHomeProject, firstWorkProject: $firstWorkProject)
                    .navigationTitle("The Process")
            }
            .tabItem { Label("Process", systemImage: "arrow.left.arrow.right") }
            .tag(2)
        }
        .onAppear {
            
            FileSaveLoad.loadTasks { result in
                switch result {
                case .failure(let error):
                    fatalError(String("\(error)"))
                case .success(let tasks):
                    taskList.tasks = tasks
                }
            }
            
            FileSaveLoad.loadProjects { result in
                switch result {
                case .failure(let error):
                    fatalError(String("\(error)"))
                case .success(let projects):
                    projectList.projects = projects
                }
            }
        }
        .onChange(of: scenePhase) { phase in
            if phase == .background {
                FileSaveLoad.saveTasks(tasks: taskList.tasks) { result in
                    if case .failure(let error) = result {
                        fatalError(String("\(error)"))
                    }
                }
                
                FileSaveLoad.saveProjects(projects: projectList.projects) { result in
                    if case .failure(let error) = result {
                        fatalError(String("\(error)"))
                    }
                }
                
            }
        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}
0 Answers
Related