How to append something to a list? - swift

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.

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 } }

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

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🙏

SwiftUI How to Disable Button While Uploading Objects in an Array

I have a SwiftUI form where the user adds images to an array from a picker and then a function fires to upload each image. The images are displayed in a ForEach as either an upload spinner while the upload is happening OR the actual image once the upload has completed.
I'd like the NEXT button, which would remove the user from the view, to be disabled until all of the uploads have completed.
I'm not sure how to inform the State of the Parent View that all of the uploads are completed.
Here's what my Parent View looks like:
struct ProjectFormStep4: View {
#EnvironmentObject var authVM: AuthorizationClass
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#ObservedObject var mediaItems = PickedMediaItems()
#State private var showSheet: Bool = false
#State private var showUploadView: Bool = false
var body: some View {
ZStack{
Color("BrandGrey").ignoresSafeArea()
VStack{
HStack{
Button{
projectVM.showProjectFormStep1 = false
projectVM.showProjectFormStep2 = false
projectVM.showProjectFormStep3 = true
projectVM.showProjectFormStep4 = false
} label: {
Text("< Back")
.font(.headline)
.foregroundColor(Color("BrandLightBlue"))
}
Spacer()
}
Spacer()
.frame(height: 30)
ZStack{
Rectangle()
.fill(Color(.white))
.frame(width: 300, height: 100)
.cornerRadius(12)
Image(systemName: "camera")
.resizable()
.foregroundColor(Color("BrandLightBlue"))
.scaledToFit()
.frame(height: 60)
}.onTapGesture {
self.showSheet = true
}
Spacer()
.sheet(isPresented: $showSheet, content: {
PhotoPicker(mediaItems: mediaItems) { didSelectItem in
showUploadView = true
showSheet = false
}
})
Spacer()
ScrollView{
ForEach(uploadVM.uploadedMedia, id:\.id){ item in
ImageArea(
item: item,
items: uploadVM.uploadedMedia
)
}
}
Spacer()
if showUploadView {
ForEach(mediaItems.items, id: \.id) { item in
UploadView(item: item)
}
}
Button {
} label: {
Text("Next")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color("BrandOrange"))
.cornerRadius(15.0)
}
}.padding()
}
}
}
Here's my Child View of which handles the upload spinner and actual image:
struct ImageArea: View {
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#State private var showThumbnailOptions: Bool = false
var item: UploadedMedia
var items : [UploadedMedia]
var body: some View {
if item.secureUrl == "" {
ZStack{
UploadSpinner()
.frame(
width: 300,
height: 200
)
.cornerRadius(12)
VStack(alignment: .leading ){
HStack{
Spacer()
.frame(
width: 30
)
Image(systemName: self.getWaterMarkName(item: item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.padding(4)
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
}
Spacer()
HStack{
Spacer()
Text("\(item.uploadProgress)%")
.foregroundColor(.black)
.font(.body)
.padding()
Spacer()
}
}
}
} else {
ZStack{
ProjectRemoteUploadImage(projectImageUrl: projectVM.standardizeThumbnail(thumbnailUrl: item.secureUrl))
.aspectRatio(contentMode: .fit)
.onTapGesture {
showThumbnailOptions.toggle()
}
if showThumbnailOptions {
Color(.black).opacity(0.8)
VStack{
Spacer()
HStack{
Spacer()
Image(systemName: "arrowshape.turn.up.backward.circle.fill")
.resizable()
.foregroundColor(.white)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
self.showThumbnailOptions.toggle()
}
Image(systemName: "trash.circle.fill")
.resizable()
.foregroundColor(.red)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
deleteThumbnail(
item: item
)
}
Spacer()
}
Spacer()
}
}
}
}
}
func getWaterMarkName(item: UploadedMedia) -> String {
if item.mediaType == "photo"{
return "photo"
} else if item.mediaType == "video" {
return "video"
} else {
return "photo"
}
}
func deleteThumbnail(item: UploadedMedia){
let itemID = item.id
guard let itemIndex = uploadVM.uploadedMedia.firstIndex(where: {$0.id == itemID}) else { return }
uploadVM.uploadedMedia.remove(at: itemIndex)
}
}
Adding model info for the uploadedMedia
struct UploadedMedia {
var id: String
var uploadProgress: Float
var secureUrl: String
var mediaType: String
var width: Int
var length: Double
init(
id: String,
uploadProgress: Float,
secureUrl: String,
mediaType: String,
width: Int,
length: Double
){
self.id = id
self.uploadProgress = uploadProgress
self.secureUrl = secureUrl
self.mediaType = mediaType
self.width = width
self.length = length
}
}

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)
}

Foreach loop inside button action block throws "Type() cannot conform to view"

I have an array of views that I need to get values from in order to save them to my context. When trying to loop over these when a button is clicked I get a "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols" exception. Im very new to swift and I am wondering if there is an easier way to do this. Thank you in advanced!
This is the complete view:
struct DetailedView: View {
#Environment(\.managedObjectContext) var moc
var targetMuscle : String = "Chest"
let today = Date()
#State public var exerciseCards : [ExerciseCard] = []
#State public var exercise : String = "Bench Press"
#State public var exercises : Int = 0
#State public var showPassedWorkouts : Bool = false
static let taskDateFormat : DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var body: some View {
ZStack{
VStack{
HStack{
VStack{
Text(targetMuscle).font(.system(size:40)).fontWeight(.medium)
Text("\(today, formatter: Self.taskDateFormat)")
.font(.system(size:20))
}.frame(width: 250, height: 30, alignment: .topLeading)
.navigationBarTitle("")
.navigationBarHidden(true)
.padding(.bottom, -7)
Button(action: {
self.showPassedWorkouts.toggle()
}) {
Text("Passed Workouts")
.multilineTextAlignment(.center)
}.offset(x: -75, y: 25)
.sheet(isPresented: $showPassedWorkouts){
PassedWorkoutList()
}
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
ForEach(0..<exerciseCards.count, id: \.self){number in
let exercise = Exercise(context: self.moc)
ForEach(0..<self.exerciseCards[number].tableRows.count, id: \.self){innerNum in
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)
}.padding(.bottom, 35)
.padding(.leading)
ScrollView{
ForEach(0..<exerciseCards.count, id: \.self){ number in
self.exerciseCards[number]
}
Button(action: {
self.exerciseCards.append(ExerciseCard())
}) {
Text("Add Exercise")
.frame(minWidth: 325)
.padding()
.foregroundColor(.white)
.background(Color.blue.opacity(0.7))
.cornerRadius(20)
}.padding(.top)
.frame(width: 400)
}
}
}.background(Color.white)
}
}
func formattedDateString(format: String? = "MMM d, h:mm", day: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: day)
}
The above view also references two views the first one being the ExerciseCard view
struct ExerciseCard: View {
#State public var exercise : String = "Bench Press"
#State public var tableRows : [ExerciseTableRow] = []
var body: some View {
VStack{
TextField("Enter Exercise", text: $exercise).textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300)
.multilineTextAlignment(.center)
HStack{
Group{
Text("Set")
Text("Weight")
Text("Reps")
}.padding(.horizontal, 30)
.offset(x: -20, y: 0)
}
VStack{
ForEach(0..<tableRows.count, id: \.self){ number in
self.tableRows[number]
}
}.padding(.bottom, 5)
HStack{
Button(action: {
if self.tableRows.count > 1{
self.tableRows.remove(at: self.tableRows.count-1)
}
}) {
Text("Remove Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(20)
}
Button(action: {
self.tableRows.append(ExerciseTableRow(set: 2, readOnly: false, setWeight: 2, setReps: 2))
}) {
Text("Add Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(20)
}
}
}
.padding()
.padding(.vertical)
.background(Color.offWhite)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.2), radius: 10, x:10, y:10)
.shadow(color: Color.white.opacity(0.7), radius: 10, x:-5, y:-5)
}
}
The second one being the ExerciseTableRow view
struct ExerciseTableRow: View {
#State public var weight : String = "0"
#State public var reps : String = "0"
var set : Int16
var readOnly : Bool
var setWeight : Int16
var setReps : Int16
var body: some View {
HStack{
Text(String(set))
.padding(.trailing, 40)
.padding(.leading, 10)
if readOnly == false{
Group{
TextField("0", text: $weight)
TextField("0", text: $reps)
}.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 50)
.multilineTextAlignment(.center)
.keyboardType(.numberPad)
.padding(.horizontal, 30)
}
else{
Group{
Text(String(setWeight))
Text(String(setReps))
}
.frame(width: 50)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.padding(.bottom, 5)
.padding(.horizontal, 30)
}
}
}
}
The ForEach is a SwiftUI dynamic View container, to build views in cycle. For action you have to use regular swift for .. in expression, like
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
for number in 0..<exerciseCards.count {
let exercise = Exercise(context: self.moc)
for innerNum in 0..<self.exerciseCards[number].tableRows.count {
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)