How to save the answers of a specific user to his profile in the questionnaire app (SwiftUI)? - swift

I am creating an app for parents or guardians of autistic children. In the app, you can create profiles for several kids and then click on the "Start Questionnaire" button in the personal card of each child. When the questionnaire page opens, there the parent answers several questions in several categories, where the questions from each category have a different weight (1, 2, 3, 4). At the end of the questionnaire, these answers are recorded in the profile of a particular kid in order to display a graph of his progress based on the answers on his personal page.
My question is that I can't figure out how to save answers to a specific kid's profile. To store profiles of children, I use CoreData, where there are four attributes: name, birthdate, dateCreated and answers. I assigned the Binary Data type to the latter. I created a #State variable in the QuestionnairePageView file and specified its type as [[String:Int]] to store a question category as a String (1, 2, 3 or 4) and an answer as an Int (yes = 1 or no = 0). Also in that file I created a saveAnswers function that assigns questionnaire answers to that #State variable when the user taps End Questionnaire button.
However, it didn't work. Please help me to understand the issue.
My data manager:
import SwiftUI
import CoreData
class CoreDataManager {
let container: NSPersistentContainer
static let shared: CoreDataManager = CoreDataManager()
private init() {
container = NSPersistentContainer(name: "PersonModel")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
}
}
My QuestinarrieApp file:
import SwiftUI
#main
struct QuestinarrieApp: App {
let controller = CoreDataManager.shared.container
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, controller.viewContext)
}
}
}
My ContentView:
import SwiftUI
import CoreData
struct ContentView: View {
#State private var isShowingAddPersonPage: Bool = false
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(fetchRequest: Person.ownFetchRequest)
var persons: FetchedResults<Person>
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10) {
ForEach(persons) { person in
let age = "\(calculateAge(fromDate: person.birthdate ?? Date()))"
NavigationLink {
PersonalPageView(person: person, name: person.name ?? "Noname", age: age)
} label: {
HStack {
Text(person.name ?? "Noname").font(.title2.bold())
Spacer()
Text(age + " years old").font(.body.bold())
}
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
.padding(.horizontal)
}
}
}
}
.navigationTitle("Persons")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
isShowingAddPersonPage = true
}) {
Image(systemName: "plus.circle")
} //: BUTTON
.sheet(isPresented: $isShowingAddPersonPage) {
AddPersonView()
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let container = CoreDataManager.shared.container
ContentView()
.environment(\.managedObjectContext, container.viewContext)
}
}
My AddPersonView file:
import SwiftUI
struct AddPersonView: View {
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) private var viewContext
#State private var name: String = ""
#State private var birthdate: Date = Date()
var body: some View {
NavigationView {
VStack(spacing: 10) {
// MARK: - NAME
HStack(spacing: 12) {
Image(systemName: "person.text.rectangle")
.resizable()
.renderingMode(.template)
.scaledToFit()
.frame(width: 25, height: 25)
TextField("Name", text: $name)
} //: HSTACK
.padding(.horizontal)
.frame(height: 60)
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
// MARK: - BIRTH DATE
HStack(spacing: 12) {
Image(systemName: "calendar")
.resizable()
.renderingMode(.template)
.scaledToFit()
.frame(width: 25, height: 25)
DatePicker(
"Birth date",
selection: $birthdate,
in: ...Date(),
displayedComponents: .date
)
.accentColor(.black)
} //: HSTACK
.padding(.horizontal)
.frame(height: 60)
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
Spacer()
// MARK: - SAVE BUTTON
Button {
if name != "" {
savePerson()
presentationMode.wrappedValue.dismiss()
}
} label: {
Text("Save")
.font(.body.bold())
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
} //: BUTTON
}
.navigationTitle("Add Person")
.padding()
}
}
private func savePerson() {
let newPerson = Person(context: viewContext)
do {
newPerson.name = name
newPerson.birthdate = birthdate
newPerson.dateCreated = Date()
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
}
struct AddPersonView_Previews: PreviewProvider {
static var previews: some View {
let container = CoreDataManager.shared.container
AddPersonView()
.environment(\.managedObjectContext, container.viewContext)
}
}
My PersonalPageView:
import SwiftUI
struct PersonalPageView: View {
#Environment(\.managedObjectContext) private var viewContext
var person: Person
var name: String
var age: String
var body: some View {
VStack(spacing: 20) {
HStack {
Text(name).font(.largeTitle.bold())
Spacer()
Text(age + " years old").font(.title2.bold()).foregroundColor(.secondary)
}
RoundedRectangle(cornerRadius: 10)
.fill(.ultraThinMaterial)
.frame(height: 300)
VStack {
ForEach(person.answers) { answer in // <- This ForEach doesn't work for now
Text("\(answer)")
}
}
Spacer()
NavigationLink {
QuestinarriePageView(person: person, name: "Jack")
} label: {
Text("Start Questinarrie")
.font(.title2.bold())
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
}
}
.navigationBarTitleDisplayMode(.inline)
.padding()
}
}
struct PersonalPageView_Previews: PreviewProvider {
static var previews: some View {
PersonalPageView(person: Person(), name: "Igor", age: "28")
}
}
And finally QuestinarriePageView:
import SwiftUI
struct QuestinarriePageView: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var answers: [[String:Int]] = [[:]]
var person: Person
var name: String
var body: some View {
VStack(spacing: 40) {
HStack {
Spacer()
Button {} label: {
Text("Quit").font(.title2)
}
.tint(.black)
}
VStack {
Text("Does \(name) like to play with other kids?")
HStack(spacing: 100) {
Button {
answers += [["1":1]]
} label: {
Text("Yes")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
Button {
answers += [["1":0]]
} label: {
Text("No")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
}
}
VStack {
Text("Does \(name) like to speak?")
HStack(spacing: 100) {
Button {
answers += [["2":1]]
} label: {
Text("Yes")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
Button {
answers += [["2":0]]
} label: {
Text("No")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
}
}
VStack {
Text("Does \(name) like to play dance?")
HStack(spacing: 100) {
Button {
answers += [["3":1]]
} label: {
Text("Yes")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
Button {
answers += [["3":0]]
} label: {
Text("No")
.foregroundColor(.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.secondary)
)
}
}
}
Spacer()
Button {
saveAnswers()
} label: {
Text("End Questionnaire")
.font(.body.bold())
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
)
}
}
.toolbar(.hidden)
.padding()
}
private func saveAnswers() {
let thisPerson = person
do {
thisPerson.answers = answers as? Data
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
}
struct QuestinarriePageView_Previews: PreviewProvider {
static var previews: some View {
QuestinarriePageView(person: Person(), name: "Igor")
}
}
Also I have a file with some helpers:
import SwiftUI
import CoreData
func calculateAge(fromDate: Date) -> Int {
let now = Date()
let calendar = Calendar.current
let ageComponents = calendar.dateComponents([.year], from: fromDate, to: now)
let age = ageComponents.year!
return age
}
extension Person {
static var ownFetchRequest: NSFetchRequest<Person> {
let request: NSFetchRequest<Person> = Person.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "dateCreated", ascending: false)]
return request
}
}
I've been struggling with this problem for a month now. I hope this is solvable🙏

Related

Swift: reload view after midnight or when a widget refreshes?

My app uses CoreData to store one quote for each day. There's a widget, the timeline of which is set to update after midnight and fetch the quote for that particular date. This works fine. However, I noticed that when I click on the widget with the new day's quote, the app - which has remained in the background - still shows the previous day's quote. Removing the app from the list of open ones and reopening it shows the correct information.
Is there a way to force a refresh of the DailyQuoteView and make sure that there isn't leftover data?
EDIT: Here's the daily quote struct that I'd like to always show the relevant data when brought from the background.
import SwiftUI
struct DailyQuoteView: View {
#Environment(\.scenePhase) var scenePhase
#State var id: UUID = .init()
#EnvironmentObject var unsplashModel: UnsplashModel
let managedObjectContext = CoreDataStack.shared.managedObjectContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)],
predicate: NSPredicate(
format: "date >= %# AND date <= %#",
argumentArray: [
Calendar.current.startOfDay(for: Date()),
Calendar.current.date(byAdding: .minute, value: 1439, to: Calendar.current.startOfDay(for: Date()))!
]
)
) var quote: FetchedResults<QuoteEntity>
#FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
) var quotes: FetchedResults<QuoteEntity>
#State var shouldPresentSheet = false
#State var animateCard = false
#Binding var cardOffset: Double
#Binding var coverImageSize: Double
#State var showAboutScreen = false
// MARK: - ELEMENTS
var coverImage: some View {
Image(uiImage: UIImage(data: quote.first?.image ?? Data()) ?? UIImage())
.resizable()
.scaledToFill()
}
var aboutButton: some View {
Button {
showAboutScreen.toggle()
} label: {
Image(systemName: "info")
.foregroundColor(.primary)
.padding(10)
.background(Circle().fill(Color("background")))
.font(.title3)
}
.padding()
.shadow(radius: 5)
}
var titleView: some View {
Text(quote.first?.title ?? "")
.font(.largeTitle)
.multilineTextAlignment(.center)
.bold()
.minimumScaleFactor(0.5)
}
var contentsBox: some View {
ScrollView {
VStack (alignment: .leading, spacing: 10) {
Text(quote.first?.summary ?? "")
.font(.body)
.multilineTextAlignment(.leading)
.padding(.bottom, 20)
if let username = quote.first?.imageUserName {
HStack(spacing: 0) {
Text("Photo: ")
Link(
destination: URL(string: "https://unsplash.com/#\(username)?utm_source=quote_of_the_day&utm_medium=referral")!,
label: {
Text((quote.first?.imageUser)!)
.underline()
})
Text(" on ")
Link(
destination: URL(string: "https://unsplash.com/")!,
label: {
Text("Unsplash")
.underline()
})
Spacer()
}
.font(.caption)
.italic()
.foregroundColor(.gray)
.padding(.vertical, 3)
}
}
.font(.subheadline)
}
}
var buttonRow: some View {
HStack {
Menu(
content: {
Button {
shouldPresentSheet.toggle()
} label: {
Text("as image")
Image(systemName: "photo")
}
ShareLink(item: quote.first!.title, message: Text(quote.first!.summary)) {
Text("as text")
Image(systemName: "text.justify.left")
}
},
label: {
HStack {
Image(systemName: "square.and.arrow.up")
Text("Share")
}
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).fill(Color("whitesand")))
.tint(Color(.secondaryLabel))
}
)
Spacer()
Link(destination: URL(string: quote.first!.link)!) {
Image(systemName: "book")
Text("Read more")
}
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).fill(Color("vpurple")))
.tint(Color(.white))
}
.frame(height: 60)
.sheet(isPresented: $shouldPresentSheet) {
QuoteShare(quote: quote.first!)
}
}
var cardView: some View {
Rectangle()
.fill((Color("background")))
.cornerRadius(50, corners: [.topLeft, .topRight])
.overlay{
VStack{
titleView
.padding(.top, 30)
contentsBox
Spacer()
buttonRow
}
.padding(.horizontal, 20)
.frame(maxWidth: 600)
}
}
// MARK: - BODY
var body: some View {
ZStack{
GeometryReader{ proxy in
coverImage
.ignoresSafeArea()
.frame(width: UIScreen.main.bounds.width, height: proxy.size.height * (animateCard ? 0.30 : 1.0))
.animation(.easeInOut(duration: 1), value: animateCard)
}
GeometryReader { proxy in
aboutButton
.frame(width: 30, height: 30)
.position(x: proxy.frame(in: .global).maxX - 30, y: proxy.frame(in: .global).minY)
}
GeometryReader{ proxy in
VStack(spacing: 0){
Spacer()
cardView
.frame(height: proxy.size.height * 0.8)
.offset(y: animateCard ? 0 : cardOffset)
.animation(.spring().delay(0.2), value: animateCard)
}
}
}
.id(id)
.blur(radius: shouldPresentSheet ? 5.0 : 0.0)
.brightness(shouldPresentSheet ? -0.2 : 0.0)
.animation(.easeIn, value: shouldPresentSheet)
.onAppear {
if !imagesLoaded {
unsplashModel.loadData(imageCount: quotes.count)
imagesLoaded = true
}
animateCard = true
}
.onChange(of: unsplashModel.dailyImageData) { _ in
if unsplashModel.dailyImageData.count == quotes.count {
updateDailyImages()
}
}
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
id = .init()
}
})
.task {
NotificationManager.shared.scheduleNotifications(notificationText: quote.first?.title ?? "")
}
}
}
EDIT 2: Code updated with lorem ipsum's suggestion below (.id and onChange of scenePhase). However, this seems to interfere with the card animation in .onAppear { withAnimation { animateCard = true } }

Can't solve scroll view in SwiftUI

Could someone please explain to me how the ScrollView works, how can I make that when I receive a message it scrolls to the last message ( item )?
I tried to make it with a, ScrollViewReader { proxy in } but I still can't make this code work.
I tried also to make it ' .onchange(of: ) { } ' (with ScrollViewReader)
struct ContentView: View{
#ObservedObject var viewModel = ViewModel()
#State var text = ""
#State var models = [String]()
var body: some View {
VStack(alignment: .leading) {
ScrollViewReader { proxy in
ScrollView (showsIndicators: false){
ForEach(models, id: \.self){ string in
Text(string)
.font(.headline)
.padding()
.background(Color("Text"))
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10).strokeBorder(.blue, style: .init(lineWidth: 1))
)
Divider()
}
.onChange(of: models){
proxy.scrollTo(models.last)
}
}
}
Spacer()
HStack{
TextField("Type here...", text: $text)
.padding(15.0)
.padding(.horizontal)
Button(){
send()
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: 40))
.foregroundColor(Color("Button"))
}
.padding(10.0)
}
.overlay(
RoundedRectangle(cornerRadius: 100)
.stroke(Color("Stroke"), lineWidth: 1)
)
}
.onAppear{
viewModel.setup()
}
.padding()
.background(/*#START_MENU_TOKEN#*//*#PLACEHOLDER=View#*/Color("Background")/*#END_MENU_TOKEN#*/)
}
func send() {
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
models.append("Me: \(self.text)")
viewModel.send(text: text) { response in
DispatchQueue.main.async {
self.models.append("Answer: "+response)
self.text = ""
}
}
}
}
..............................
use a different overload
proxy.scrollTo(models.last,anchor:.top)

How to append something to a list?

I'm new to Swift at the moment and I'm having trouble appending to a to-do list. I am not receiving an error, so I don't know what's wrong. I hope this isn't too much code to go through, but I don't even know where the problem is.
There is a sheet modal that opens and closes fine. The only issue is that when I press the save button, the information I typed in doesn't add to the list.
I've add the ViewModel, NewTaskView(sheet), TaskView(customizes list), and a bit of code of the DetailView where the list should be.
import Foundation
import SwiftUI
class TaskViewModel : Identifiable , ObservableObject {
#Published var tasks : [Task] = [
Task(taskName: "Lab", taskDate: Date(), taskCompleted: false),
Task(taskName: "Assignment 4.02", taskDate: Date(), taskCompleted: false)
]
#Published var sortType : SortType = .alphabetical
#Published var isPresented = false
#Published var searched = ""
func addTask(task : Task){
tasks.append(task)
}
func removeTask(indexAt : IndexSet){
tasks.remove(atOffsets: indexAt)
}
func sort(){
switch sortType {
case .alphabetical :
tasks.sort(by: { $0.taskName < $1.taskName })
case .date :
tasks.sort(by: { $0.taskDate > $1.taskDate })
}
}
}
struct Task : Identifiable , Equatable {
var id : String = UUID().uuidString
let taskName : String
let taskDate : Date
var taskCompleted: Bool
}
enum SortType : String , Identifiable , CaseIterable {
var id : String { rawValue }
case alphabetical
case date
}
struct NewTaskView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var taskVM : TaskViewModel
#State var taskName = ""
#State var taskCompleted = Bool()
#State var taskDate = Date()
var body: some View {
NavigationView {
VStack(spacing: 14) {
Spacer()
TextField("Assignment Name",text: $taskName)
.padding()
.background(Color("tan"))
.cornerRadius(5)
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
.padding(.bottom, 20)
HStack{
Text("Due Date")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Image(systemName: "bell.badge")
.foregroundColor(.gray)
.frame(width: 30, height: 30, alignment: .leading)
VStack{
DatePicker("Select Date", selection: $taskDate, displayedComponents: .date)
.labelsHidden()
}
}
HStack{
Text("Mark as Completed")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Button(action : {
taskCompleted.toggle()
}){
if taskCompleted == true {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}
}
Spacer()
Button(action:{taskVM.addTask(task: .init(taskName: taskName, taskDate: taskDate, taskCompleted: taskCompleted))
presentationMode.wrappedValue.dismiss()},
label:{
Text("Save")
})
}.padding()
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack{
Image(systemName: "xmark")
.foregroundColor(Color("orange"))
}
})
}
}
}
struct NewTaskView_Previews: PreviewProvider {
static var previews: some View {
NewTaskView(taskVM: TaskViewModel())
}
}
struct TaskView: View {
var task : Task
#Environment(\.managedObjectContext) var moc
#State var currentDate: Date = Date()
#State var taskCompleted = false
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
HStack {
Button(action : {
self.taskCompleted.toggle()
try? self.moc.save()
}){
if self.taskCompleted {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}.padding()
VStack (alignment: .leading, spacing: 2){
Text("\(task.taskName)")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 18))
.foregroundColor(Color("dark"))
.padding([.leading, .trailing], 1)
if self.taskCompleted {
Text("Completed")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
else if currentDate > task.taskDate {
Text("Late")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(.white)
.frame(width: 95, height: 18)
.background(Color("lightbrown"))
.cornerRadius(4)
}
else {
let diffs = Calendar.current.dateComponents([.day], from: currentDate, to: task.taskDate )
Text("\(diffs.day!) days left")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
}
Spacer()
Text(getDateFormatString(date: task.taskDate ))
.font(Font.custom(FontNameManager.Montserrat.medium, size: 12))
.padding()
}
.padding(10)
.cornerRadius(10)
.background(
RoundedRectangle(cornerRadius: 10 , style: .continuous)
.foregroundColor(.white))
}
}
struct TaskView_Previews: PreviewProvider {
static var previews: some View {
TaskView(task: Task(id: "", taskName: "Task Name", taskDate: Date(), taskCompleted: false))
}
}
struct DetailsView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var developers : Developer
#ObservedObject var taskVM : TaskViewModel
#Environment(\.presentationMode) var presen
#Environment(\.editMode) var editButton
#State var taskCompleted = false
#State var show = false
#State var showSearch = false
#State var txt = ""
#State var currentDate: Date = Date()
#State var image : Data = .init(count: 0)
#State var indices : [Int] = []
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
NavigationView {
ZStack {
ZStack{
VStack{
Color("lightorange")
.clipShape(CustomCorners(corners: [.bottomLeft, .bottomRight], size: 70))
.ignoresSafeArea(.all, edges: .top)
.frame(width: 420, height: 175)
Spacer()
}
VStack{
HStack {
if !self.showSearch{
Button(action: {
withAnimation(.spring()){
self.presen.wrappedValue.dismiss()
}
}) {
Image(systemName: "chevron.left")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
Spacer(minLength: 0)
HStack {
if self.showSearch{
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(.horizontal, 8)
TextField("Search", text: $taskVM.searched , onEditingChanged: { (isBegin) in
if isBegin {
showSearch = true
} else {
showSearch = false
}
})
Button(action: {
withAnimation{
self.showSearch.toggle()
}
}) {
Image(systemName: "xmark").foregroundColor(.black).padding(.horizontal, 8)
}
}
else {
Button(action: {
withAnimation {
self.showSearch.toggle()
}
}) {
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
}.padding(self.showSearch ? 10 : 0)
.background(Color("lightorange"))
.cornerRadius(20)
}
ZStack{
RoundedRectangle(cornerRadius: 25)
.foregroundColor(Color("tan"))
.frame(width: 335, height: 130)
HStack {
VStack (alignment: .leading){
// Image(uiImage: UIImage(data: developers.imageD ?? self.image)!)
// .resizable()
// .frame(width: 70, height: 70)
// .clipShape(RoundedRectangle(cornerRadius: 20))
Text("\(developers.username ?? "")")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 24))
.foregroundColor(Color("dark"))
Text("\(developers.descriptions ?? "")")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 18))
.foregroundColor(.gray)
}
.padding([.leading, .trailing], 70)
.padding(.bottom, 80)
Spacer()
}
}
HStack{
Text("Assignments")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 20))
Spacer(minLength: 0)
EditButton()
}
.padding([.leading, .trailing], 20)
Spacer()
List {
ForEach (taskVM.tasks.filter {
self.taskVM.searched.isEmpty ? true : $0.taskName.localizedCapitalized.contains(self.taskVM.searched)} ){ task in
TaskView(task: task)
.listRowSeparator(.hidden)
}
.onDelete(perform: {
taskVM.removeTask(indexAt: $0)
})
}
.listStyle(InsetListStyle())
}
}
VStack {
Spacer()
HStack {
Spacer()
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
}
}
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
struct DetailsView_Previews: PreviewProvider {
static let persistenceController = PersistenceController.shared
static var developers: Developer = {
let context = persistenceController.container.viewContext
let developers = Developer(context: context)
developers.username = "Math"
developers.descriptions = "Calculus"
return developers
}()
static var previews: some View {
DetailsView(developers: developers, taskVM: TaskViewModel()).environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
struct ExpandableFAB: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var taskVM : TaskViewModel
#Binding var show: Bool
var body: some View{
VStack(spacing: 20){
if self.show{
Button(action: {
taskVM.isPresented.toggle()
}) {
Image(systemName: "list.bullet.rectangle.portrait").resizable().frame(width: 25, height: 25).padding()
}
.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
Button(action: {
self.show.toggle()
}) {
Image(systemName: "doc.badge.plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
}
Button(action: {
self.show.toggle()
}) {
Image(systemName: "plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding()
.rotationEffect(.init(degrees: self.show ? 180 : 0))
}.fullScreenCover(isPresented: $taskVM.isPresented, content: {
NewTaskView(taskVM: taskVM)
})
}
}
#burnsi I don't think I have that anywhere. Where should there be one?
Of course you have one and you need one:
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
This line is causing the issue.
You are somehow injecting a TaskViewModel in your DetailsView (You are not showing where), but not using it. Try:
ExpandableFAB(taskVM: taskVM, show: $show)
And the place you create your ViewModel ('TaskViewModel()') should have a #StateObject wrapper.
Explanation:
As you add something to your ViewModel the views depending on the ViewModel get rebuild. So your TaskViewModel ends up beeing recreated and you still have your 2 initial values in your array.

How do I pass a var from one viewModel to a new viewModel in my .fullScreenCover() View?

I'm building an app with swiftUI & firebase/firestore. I open a fullscreenCover sheet of a product selected from a catalog and the user can add it to a list below it. The product is selected on the previous page, and that is passed onto the .fullScreeenCover, where I'm introducing a new ViewModel for the list.
Where I'm getting confused: How do I pass the product ID passed into this fullScreenCover view into my newly introduced list's viewModel so that I can run the "add to list" function?
ViewModel for my List:
import SwiftUI
import Firebase
class ListViewModel: ObservableObject {
let var product: Product
#Published var userList = [List]()
#Published var list: List
init(list: List) {
self.list = list
}
func fetchList() {
let docRef = Firestore.firestore().collection("List")
guard let uid = AuthViewModel.shared.userSession?.uid else { return }
docRef.whereField("uid", isEqualTo: uid).getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
self.userList = documents.map({ List(dictionary: $0.data())} )}
}
func AddProductToList(product: Product, list: List) {
let listRef = Firestore.firestore().collection("List").document(list.id).collection("Products")
let productRef = Firestore.firestore().collection("Products").document(product.id)
productRef.getDocument { snapshot, _ in
listRef.document(self.product.id).setData([:]) { _ in
print("\(self.product.title) was saved to \(self.list.name)")
}
}
}
}
Code for .fullScreenCover() sheet View
import SwiftUI
struct ListCoverView: View {
#Binding var isPresented: Bool
let viewModel: LikeViewModel
#StateObject var listViewModel = ListViewModel(list)
var body: some View {
ZStack {
VStack (spacing: 10) {
VStack {
WebImage(url: URL(string: viewModel.product.image))
.resizable()
.frame(width: 220, height: 220)
.padding(.top,10)
Text("\(viewModel.product.title)")
.fontWeight(.bold)
.foregroundColor(.black)
.padding(.horizontal)
Text(viewModel.product.company)
.foregroundColor(.black)
.fontWeight(.bold)
}
.onAppear(perform: {
// Fetch products
listViewModel.fetchList()
})
ScrollView {
VStack {
Button(action: listViewModel.AddProductToList(Product)) {
ForEach(listViewModel.userList){list in
ListRow(list: listViewModel.list, viewModel: listViewModel)
}
}
}
}
}
}
Spacer()
Button(action: {isPresented.toggle()}, label : {
Text("Close")
.font(.system(size: 20))
.foregroundColor(.black)
})
.padding()
}
}
Parent View of the .fullScreenCover()
import SwiftUI
import SDWebImageSwiftUI
struct CardView: View {
let product: Product
#ObservedObject var viewModel: LikeViewModel
#State private var isShowingNewListSheet = false
init(product: Product) {
self.product = product
self.viewModel = LikeViewModel(product: product)
}
var body: some View {
VStack {
WebImage(url: URL(string: product.image))
.resizable()
.aspectRatio(contentMode: .fit)
Text(product.title)
.fontWeight(.bold)
.foregroundColor(.black)
.padding(.horizontal)
Text(product.company)
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.trailing,65)
HStack{
Button(action: {
viewModel.didLike ? viewModel.UnlikeProduct() : viewModel.LikeProduct()
}, label : {
Image(systemName: viewModel.didLike ? "heart.fill" : "heart")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(viewModel.didLike ? .red : .black)
})
.padding(.trailing,5)
Button(action: { isShowingNewListSheet.toggle()
}, label : {
Image(systemName: "square.and.arrow.down")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.black)
})
.fullScreenCover(isPresented: $isShowingNewListSheet) {
ListCoverView(isPresented: $isShowingNewListSheet, viewModel: viewModel)
}
}
}
.padding(.bottom)
.background(Color(.white))
.cornerRadius(15)
}
}

CoreData data not showing up in my list view

I have a problem with CoreData...I've managed to get it to work, but it won't show my title property (which is the task name). I'm pretty new at this, I hope someone can help me out.
MainUI <--this is a pic of what the UI looks like and where the problem is.
This is the main ViewController
import SwiftUI
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
#State var isPresented = false
var body: some View {
VStack {
NavigationView {
ZStack {
List{
ForEach(tasks, id: \.id){ task in
CellView(completionState: task.completionState, title: task.title!)
}
}
Button(action: {
self.isPresented.toggle()
print(isPresented)
}) {
CircleView()
}.offset(x: 158, y: 250)
}
.navigationBarTitle("Infinito")
.sheet(isPresented: $isPresented, content: {
EditView()
.environment(\.managedObjectContext, self.moc)
})
}
HStack(alignment: .center, spacing: 97) {
Button(action: {}) {
Image(systemName: "highlighter")
.resizable()
.frame(width: 45, height: 45)
.padding()
.foregroundColor(.orange)
}.offset(x: 10)
Button(action: {}){
Image(systemName: "timelapse")
.resizable()
.frame(width: 45, height: 45)
.offset(x: 4, y: 0)
.padding()
.foregroundColor(.green)
}.offset(x: -7)
.padding(.trailing, 20.0)
Button(action: {}){
Image(systemName: "alarm")
.resizable()
.frame(width: 45, height: 45)
.foregroundColor(.red)
}.offset(x: -24)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
}
struct CellView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
var completionState: Bool
var title: String
var body: some View {
HStack{
switch completionState{
case false:
Image(systemName: "square")
case true:
Image(systemName: "checkmark.square")
.foregroundColor(.green)
Text(title)
.foregroundColor(.black)
}
}
}
}
And this is the code for the sheet view
//
// EditView.swift
// Infinito
//
// Created by Armando Visini on 28/10/2020.
//
import SwiftUI
struct EditView: View{
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
#State var titleOfTask: String = ""
var body: some View {
NavigationView {
VStack(alignment: .leading){
Text("Title of task")
.font(.title)
.fontWeight(.bold)
HStack {
TextField("Enter name of task here...", text: $titleOfTask)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
let task = Task(context: self.moc)
task.id = UUID()
task.title = titleOfTask
task.completionState = false
try? self.moc.save()
UIApplication.shared.endEditing()
}) {
Text("Confirm")
.foregroundColor(.white)
.fontWeight(.medium)
.background(Color(.blue))
.cornerRadius(6.0)
}
}
}
.padding()
.offset(y: -200.0)
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
}
}
extension UIApplication{
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
//
// EditView.swift
// Infinito
//
// Created by Armando Visini on 28/10/2020.
//
import SwiftUI
struct EditView: View{
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
#State var titleOfTask: String = ""
var body: some View {
NavigationView {
VStack(alignment: .leading){
Text("Title of task")
.font(.title)
.fontWeight(.bold)
HStack {
TextField("Enter name of task here...", text: $titleOfTask)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
let task = Task(context: self.moc)
task.id = UUID()
task.title = titleOfTask
task.completionState = false
try? self.moc.save()
UIApplication.shared.endEditing()
}) {
Text("Confirm")
.foregroundColor(.white)
.fontWeight(.medium)
.background(Color(.blue))
.cornerRadius(6.0)
}
}
}
.padding()
.offset(y: -200.0)
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
}
}
extension UIApplication{
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
It looks like inside your CellView your not closing the switch statement, this will probably cause that your Text is only displayed when the checkbox ist checked. (completionState is true) Place your Text that contains your title outside the switch statement:
HStack {
switch completionState {
case false:
Image(systemName: "square")
case true:
Image(systemName: "checkmark.square")
.foregroundColor(.green)
}
Text(title)
.foregroundColor(.black)
}