Core Data NSManagedObject - ObservedObjects not updating - ios

I have a data entry view that lets the user add an image and see the preview after doing so.
The ImagePickerView I have returns a UIImage which I save to Core Data as type Data through the .pngData converter. However, after selecting the image, the view does not update to show it even though I am using #ObservedObject and objectWillChange
I can't use #State because the draft object is an NSManagedObject
import SwiftUI
import CoreData
struct AddItemView: View {
#Environment(\.managedObjectContext) var moc
#Environment (\.presentationMode) var presentationMode
#State var showImagePicker: Bool = false
#ObservedObject var draft: Item //Core Data entity
var body: some View {
NavigationView {
VStack {
if (draft.image != nil) {
Image(uiImage: UIImage(data: draft.image!)!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: UIScreen.main.bounds.width - 32, height: UIScreen.main.bounds.height / 4)
.clipShape(RoundedRectangle(cornerRadius: 20))
} else {
Button(action: {
self.showImagePicker = true
}, label: {
RoundedRectangle(cornerRadius: 20)
})
.padding([.all], 20)
}
}
.sheet(isPresented: $showImagePicker) {
ImagePickerView(sourceType: .photoLibrary) { image in
draft.objectWillChange.send()
draft.image = image.pngData()
}
}
}
}
}

Try using objectWillChange.send() after you make your changes:
.sheet(isPresented: $showImagePicker) {
ImagePickerView(sourceType: .photoLibrary) { image in
draft.image = image.pngData() // #1
draft.objectWillChange.send() // #2
}
}

Related

Using ImagePicker in SwiftUI

I am new to SwiftUI. I want to make an app that has an image picker.
I found this article: https://ishtiz.com/swiftui/image-picker-in-swiftui
It says:
To implement an image picker in SwiftUI, you can use the ImagePicker struct provided by the SwiftUI framework. This struct has a pickImage() method that presents the image picker to the user and returns the selected image as a UIImage object.
Providing this example code:
struct ContentView: View {
#State private var image: UIImage?
var body: some View {
VStack {
if image != nil {
Image(uiImage: image!)
.resizable()
.scaledToFit()
}
Button("Select Image") {
self.image = ImagePicker.pickImage()
}
}
}
}
I added the code to my project but it doesn’t build:
Cannot find 'ImagePicker' in scope
Do I need to import something?
try import PhotosUI
For iOS Versions lower than 16.0
you can refers tutorial
For iOS 16.0 + you can use PhotoPicker
import PhotosUI
import SwiftUI
#available(iOS 16.0, *)
struct PhotosPickerDemo: View {
#State private var selectedItem: PhotosPickerItem? = nil
#State private var selectedImageData: Data? = nil
var body: some View {
PhotosPicker(
selection: $selectedItem,
matching: .images,
photoLibrary: .shared()) {
Text("Select a photo")
}
.onChange(of: selectedItem) { newItem in
Task {
// Retrieve selected asset in the form of Data
if let data = try? await newItem?.loadTransferable(type: Data.self) {
selectedImageData = data
}
}
}
if let selectedImageData,
let uiImage = UIImage(data: selectedImageData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250)
}
}
}

How do I let text retrieved from Firestore load upon the view being shown?

Intro
Hi there. I recently asked a different question asking how to implement user-triggered FCMs. I quickly realised that in order to implement FCMs I needed to add another feature to my app, which is why I am here now. I'm trying, trust me.
My question / problem
In the picture I attached, you can see the text that is supposed to show when the user clicks on the friends tab. But it doesn't do that. It does when I refresh the list, because I put the function in the refreshable attribute. I did also put the function in the view initialisation, but it doesn't do what I want it to do. So my question would be, a) why it doesn't load? and b) what would be an approach to solve my problem?
Code reference
This function is called in the init{} of the view and .refreshable{} of the list inside the view. (I also tried adding it via .onAppear{} in the NavigationLink of the parent view.)
#State var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let bestie = document.get("bestie") as? String ?? "error: bestie"
bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
Image for reference
Thanks and annotation
Thank you very much in advance, I'm amazed every day by how amazing this community is and how so many people are willing to help. If you need me to add anything else, of course I will do that. I hope I'll be wise enough one day to help other people with their problems as well.
Edit
The view
import Firebase
import FirebaseAuth
import SDWebImage
import SDWebImageSwiftUI
import SwiftUI
struct View_Friend_Tab: View {
#ObservedObject var friends_model = Model_User()
#State var friendWho = ""
init() {
friends_model.getFriendlist()
//friends_model.getBestie()
getBestie()
}
//VARS
let gifurl = URL(string: "https://c.tenor.com/BTCEb08QgBgAAAAC/osita-iheme-aki-and-pawpaw.gif")
let avatarURL = URL(
string:
"https://firebasestorage.googleapis.com/v0/b/universerp-72af2.appspot.com/o/avatars%2Fanime-girl-white-hair-1-cropped.jpg?alt=media&token=efba4215-850d-41c8-8c90-385f7a572e94"
)
#State var showingAlert = false
#State var showFriendRequests = false
#State var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let bestie = document.get("bestie") as? String ?? "error: bestie"
bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
var body: some View {
if !showFriendRequests {
VStack {
NavigationView {
/*
List (friends_model.friend_list) { item in
HStack {
Text(item.email)
Spacer()
}
}
.refreshable{
friends_model.getFriendlist()
}
.listStyle(.grouped)
*/
List {
Section(header: Text("management")) {
NavigationLink(destination: View_Friend_Best_Tab()) {
Label("Select your bestie", systemImage: "star.fill")
}
NavigationLink(destination: View_Friend_Requests_Tab()) {
Label("Manage friend requests", systemImage: "person.fill.questionmark")
}
NavigationLink(destination: View_Friend_Add_Tab()) {
Label("Add friend", systemImage: "person.fill.badge.plus")
}
}
ForEach(friends_model.friend_list) { item in
let avURL = URL(string: item.avatarURL)
Section(header: Text(item.username)) {
HStack {
VStack(alignment: .leading) {
if bestfriend == item.email {
Text("Is your Shin'yū")
.foregroundColor(Color("lightRed"))
.fontWeight(.bold)
.font(.footnote)
}
Text(item.username)
.fontWeight(.bold)
.frame(alignment: .leading)
Text(item.email)
.font(.footnote)
.multilineTextAlignment(.leading)
}
Spacer()
WebImage(url: avURL)
.resizable(resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.clipShape(Circle())
.shadow(radius: 5)
.overlay(Circle().stroke(Color.black, lineWidth: 1))
}
Button("Remove", role: .destructive) {
showingAlert = true
}
.alert("Do you really want to remove this friend?", isPresented: $showingAlert) {
HStack {
Button("Cancel", role: .cancel) {}
Button("Remove", role: .destructive) {
friendWho = item.email
removeFriend()
withAnimation {
friends_model.getFriendlist()
}
}
}
}
}
}
}
.navigationTitle("Your Friends")
.navigationViewStyle(.automatic)
.refreshable {
friends_model.getFriendlist()
getBestie()
}
.listStyle(.insetGrouped)
Spacer()
}
}
} else {
View_Friend_Requests_Tab()
}
}
}
struct View_Friend_Tab_Previews: PreviewProvider {
static var previews: some View {
View_Friend_Tab()
}
}
As previously stated, the function is being called in the init block and when the list is refreshed.
As a general recommendation, use view models to keep your view code clean.
In SwiftUI, a view's initialiser should not perform any expensive / long-running computations. Keep in mind that in SwiftUI, a view is only a description of your UI, not the UI itself. Any state management should be handled outside of the initialiser.
In your case, use .onAppear or .task:
import Firebase
import FirebaseAuth
import SDWebImage
import SDWebImageSwiftUI
import SwiftUI
class FriendsViewModel: ObservableObject {
#Published var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
// consider using Codable for mapping - see https://peterfriese.dev/posts/firestore-codable-the-comprehensive-guide/
let bestie = document.get("bestie") as? String ?? "error: bestie"
self.bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
// add other functions and properties here.
}
struct FriendsView: View {
#ObservedObject var viewModel = FriendsViewModel()
//VARS
let gifurl = URL(string: "https://c.tenor.com/BTCEb08QgBgAAAAC/osita-iheme-aki-and-pawpaw.gif")
let avatarURL = URL(
string:
"https://firebasestorage.googleapis.com/v0/b/universerp-72af2.appspot.com/o/avatars%2Fanime-girl-white-hair-1-cropped.jpg?alt=media&token=efba4215-850d-41c8-8c90-385f7a572e94"
)
#State var showingAlert = false
#State var showFriendRequests = false
#State var bestfriend: String = ""
var body: some View {
if !showFriendRequests {
VStack {
NavigationView {
List {
Section(header: Text("management")) {
NavigationLink(destination: SelectBestieView()) {
Label("Select your bestie", systemImage: "star.fill")
}
NavigationLink(destination: ManageFriendRequestsView()) {
Label("Manage friend requests", systemImage: "person.fill.questionmark")
}
NavigationLink(destination: AddFriendsView()) {
Label("Add friend", systemImage: "person.fill.badge.plus")
}
}
ForEach(viewModel.friends) { friend in
let avURL = URL(string: friend.avatarURL)
Section(header: Text(friend.username)) {
HStack {
VStack(alignment: .leading) {
if bestfriend == friend.email {
Text("Is your Shin'yū")
.foregroundColor(Color("lightRed"))
.fontWeight(.bold)
.font(.footnote)
}
Text(friend.username)
.fontWeight(.bold)
.frame(alignment: .leading)
Text(friend.email)
.font(.footnote)
.multilineTextAlignment(.leading)
}
Spacer()
WebImage(url: avURL)
.resizable(resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.clipShape(Circle())
.shadow(radius: 5)
.overlay(Circle().stroke(Color.black, lineWidth: 1))
}
Button("Remove", role: .destructive) {
showingAlert = true
}
.alert("Do you really want to remove this friend?", isPresented: $showingAlert) {
HStack {
Button("Cancel", role: .cancel) {}
Button("Remove", role: .destructive) {
friendWho = friend.email
viewModel.removeFriend()
withAnimation {
viewModel.getFriendlist()
}
}
}
}
}
}
}
.navigationTitle("Your Friends")
.navigationViewStyle(.automatic)
.onAppear {
viewModel.getFriendlist()
viewModelgetBestie()
}
.refreshable {
viewModel.getFriendlist()
viewModelgetBestie()
}
.listStyle(.insetGrouped)
Spacer()
}
}
} else {
FriendRequestsView()
}
}
}
struct FriendsViewPreviews: PreviewProvider {
static var previews: some View {
FriendsView()
}
}

SwiftUI: How to select multi items(image) with ForEach?

I'm working on my project with the feature of select multiple blocks of thumbnails. Only selected thumbnail(s)/image will be highlighted.
For the ChildView, The binding activeBlock should be turned true/false if a use taps on the image.
However, when I select a thumbnail, all thumbnails will be highlighted.I have come up with some ideas like
#State var selectedBlocks:[Bool]
// which should contain wether or not a certain block is selected.
But I don't know how to implement it.
Here are my codes:
ChildView
#Binding var activeBlock:Bool
var thumbnail: String
var body: some View {
VStack {
ZStack {
Image(thumbnail)
.resizable()
.frame(width: 80, height: 80)
.background(Color.black)
.cornerRadius(10)
if activeBlock {
RoundedRectangle(cornerRadius: 10)
.stroke(style: StrokeStyle(lineWidth: 2))
.frame(width: 80, height: 80)
.foregroundColor(Color("orange"))
}
}
}
BlockBView
struct VideoData: Identifiable{
var id = UUID()
var thumbnails: String
}
struct BlockView: View {
var videos:[VideoData] = [
VideoData(thumbnails: "test"), VideoData(thumbnails: "test2"), VideoData(thumbnails: "test1")
]
#State var activeBlock = false
var body: some View {
ScrollView(.horizontal){
HStack {
ForEach(0..<videos.count) { _ in
Button(action: {
self.activeBlock.toggle()
}, label: {
ChildView(activeBlock: $activeBlock, thumbnail: "test")
})
}
}
}
}
Thank you for your help!
Here is a demo of possible approach - we initialize array of Bool by videos count and pass activated flag by index into child view.
Tested with Xcode 12.1 / iOS 14.1 (with some replicated code)
struct BlockView: View {
var videos:[VideoData] = [
VideoData(thumbnails: "flag-1"), VideoData(thumbnails: "flag-2"), VideoData(thumbnails: "flag-3")
]
#State private var activeBlocks: [Bool] // << declare
init() {
// initialize state with needed count of bools
self._activeBlocks = State(initialValue: Array(repeating: false, count: videos.count))
}
var body: some View {
ScrollView(.horizontal){
HStack {
ForEach(videos.indices, id: \.self) { i in
Button(action: {
self.activeBlocks[i].toggle() // << here !!
}, label: {
ChildView(activeBlock: activeBlocks[i], // << here !!
thumbnail: videos[i].thumbnails)
})
}
}
}
}
}
struct ChildView: View {
var activeBlock:Bool // << value, no binding needed
var thumbnail: String
var body: some View {
VStack {
ZStack {
Image(thumbnail)
.resizable()
.frame(width: 80, height: 80)
.background(Color.black)
.cornerRadius(10)
if activeBlock {
RoundedRectangle(cornerRadius: 10)
.stroke(style: StrokeStyle(lineWidth: 2))
.frame(width: 80, height: 80)
.foregroundColor(Color.orange)
}
}
}
}
}
Final result
Build your element and it's model first. I'm using MVVM,
class RowModel : ObservableObject, Identifiable {
#Published var isSelected = false
#Published var thumnailIcon: String
#Published var name: String
var id : String
var cancellables = Set<AnyCancellable>()
init(id: String, name: String, icon: String) {
self.id = id
self.name = name
self.thumnailIcon = icon
}
}
//Equivalent to your BlockView
struct Row : View {
#ObservedObject var model: RowModel
var body: some View {
GroupBox(label:
Label(model.name, systemImage: model.thumnailIcon)
.foregroundColor(model.isSelected ? Color.orange : .gray)
) {
HStack {
Capsule()
.fill(model.isSelected ? Color.orange : .gray)
.onTapGesture {
model.isSelected = !model.isSelected
}
//Two way binding
Toggle("", isOn: $model.isSelected)
}
}.animation(.spring())
}
}
Prepare data and handle action in your parent view
struct ContentView: View {
private let layout = [GridItem(.flexible()),GridItem(.flexible())]
#ObservedObject var model = ContentModel()
var body: some View {
VStack {
ScrollView {
LazyVGrid(columns: layout) {
ForEach(model.rowModels) { model in
Row(model: model)
}
}
}
if model.selected.count > 0 {
HStack {
Text(model.selected.joined(separator: ", "))
Spacer()
Button(action: {
model.clearSelection()
}, label: {
Text("Clear")
})
}
}
}
.padding()
.onAppear(perform: prepare)
}
func prepare() {
model.prepare()
}
}
class ContentModel: ObservableObject {
#Published var rowModels = [RowModel]()
//I'm handling by ID for futher use
//But you can convert to your Array of Boolean
#Published var selected = Set<String>()
func prepare() {
for i in 0..<20 {
let row = RowModel(id: "\(i)", name: "Block \(i)", icon: "heart.fill")
row.$isSelected
.removeDuplicates()
.receive(on: RunLoop.main)
.sink(receiveValue: { [weak self] selected in
guard let `self` = self else { return }
print(selected)
if selected {
self.selected.insert(row.name)
}else{
self.selected.remove(row.name)
}
}).store(in: &row.cancellables)
rowModels.append(row)
}
}
func clearSelection() {
for r in rowModels {
r.isSelected = false
}
}
}
Don't forget to import Combine framework.

SwiftUI: Cannot Delete List Item If Just Viewed

I have a very odd issue. I have a list app that crashes when I delete a list item that I just viewed. I can delete an item that is different than the one I just viewed without the crash. The crash error is:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/XXX/Documents/Xcode Projects Playground/Test-Camera-CloudKit/Test-Camera-CloudKit/DetailView.swift, line 20
Line 20 of the DetailView.swift file is the line that displays the image/photo [Image(uiImage: UIImage(data: myItem.photo!) ?? UIImage(named: "gray_icon")!)]. Below are the files from my stripped down app to try to run this issue to ground. I am using CoreData and CloudKit.
ContentView.swift:
import SwiftUI
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]) var items: FetchedResults<Item>
#State private var showingAddScreen = false
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
NavigationLink(destination: DetailView(myItem: item)) {
HStack {
Text(item.name ?? "Unknown name")
}
}
}.onDelete(perform: delete)
}
.navigationBarTitle("Items")
.navigationBarItems(trailing:
Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddScreen) {
AddItemView().environment(\.managedObjectContext, self.moc)
}
}
}
func delete(at offsets: IndexSet) {
for index in offsets {
let item = items[index]
moc.delete(item)
}
do {
try moc.save()
} catch {
print("Error deleting objects")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddItemView.swift:
import SwiftUI
struct AddItemView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
#State private var image : Data = .init(count: 0)
#State private var name = ""
#State private var show = false
var body: some View {
NavigationView {
Form {
Section {
TextField("Name of item", text: $name)
}
Section {
VStack {
Button(action: {self.show = true}) {
HStack {
Image(systemName: "camera")
}
}
Image(uiImage: UIImage(data: self.image) ?? UIImage(named: "gray_icon")!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 300, alignment: Alignment.center)
.clipped()
}
.sheet(isPresented: self.$show, content: {
ImagePicker(show: self.$show, image: self.$image)
})
}
Section {
Button("Save") {
let newItem = Item(context: self.moc)
newItem.name = self.name
newItem.photo = self.image
try? self.moc.save()
self.presentationMode.wrappedValue.dismiss()
}
}
}
.navigationBarTitle("Add Item")
}
}
}
struct AddItemView_Previews: PreviewProvider {
static var previews: some View {
AddItemView()
}
}
DetailView.swift
import SwiftUI
struct DetailView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var myItem: Item
var body: some View {
VStack {
Text(myItem.name ?? "Unknown")
Image(uiImage: UIImage(data: myItem.photo!) ?? UIImage(named: "gray_icon")!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 300, alignment: Alignment.center)
.clipped()
}
.navigationBarTitle(Text(myItem.name ?? "Unknown"), displayMode: .inline)
}
}
struct DetailView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let item = Item(context: moc)
item.name = "Test"
return NavigationView {
DetailView(myItem: item)
}
}
}
ImagePicker.swift
import SwiftUI
import Combine
struct ImagePicker : UIViewControllerRepresentable {
#Binding var show : Bool
#Binding var image : Data
func makeCoordinator() -> ImagePicker.Coordinator {
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.show.toggle()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[.originalImage]as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.show.toggle()
}
}
}
I am really struggling with how does the app have an error in a view that is not being shown when an item is deleted. The list items does get deleted and removed from CloudKit. The delete operation works. The crash and error happens whether the coredata attribute for the photo has a photo or not. In other words, it has the error even when the item does have a photo and is not nil. Where am I going wrong? How do I get it to allow me to delete an item that I just viewed in the DetailView without a nil error? Any help is greatly appreciated. Thanks.

A View with an ObservedObject and nothing will allow me to edit it

https://github.com/ryanpeach/RoutinesAppiOS
A Picker will not move, the Edit Button won't click, and a Text Field won't enter more than 1 character. Return on keyboard doesn't work. Most of these are tied to bindings to the ObservedObject and should be able to directly edit it. I believe there is a common cause with CoreData hanging on object updates. The done button on the final player view hangs, but the skip button does not! That means it only happens when TaskData is updated. If you delete an Alarm in certain situations the app crashes too.
Here's a video of the app behavior so far. In the view right before the end nothing will click. At the final view it hangs when you click the checkmark.
https://www.icloud.com/photos/#0HMiYqZ08ZYoFu5BEQXET4gRA
I am seeking some tips on how to debug this error. I can't put a breakpoint on the picker "when something changes" and I similarly cant do it on the Text Field. Why would a text field only take one character and then stop? Why would the edit button not work? This is the only view in the app where these sub-views don't work, the rest of the app works fine.
Some relevant background information:
I'm using coredata. There are 3 classes: AlarmData for the Routines Page, which has a one2many relationship to TaskData for the TaskList Page, which has a one2many relationship to SubTaskData for the TaskPlayerView and TaskEditor pages, the ones I'm having trouble with.
No further relationships.
I'm doing a fetchrequest at the root view and then using #ObservedObject the rest of the way down the view hierarchy. I'm using mostly isActive and tag:selection NavigationLinks.
The relevant file:
struct TaskEditorView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var taskData: TaskData
#State var newSubTask: String = ""
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
var body: some View {
VStack {
TitleTextField(text: self.$taskData.name)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
TimePickerRelativeView(time: self.$taskData.duration)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
HStack {
Spacer().frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: DEFAULT_HEIGHT_SPACING)
ReturnTextField(
label: "New Subtask",
text: self.$newSubTask,
onCommit: self.addSubTask
)
Button(action: {
self.addSubTask()
}) {
Image(systemName: "plus")
.frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: 30)
}
Spacer().frame(width: DEFAULT_HEIGHT_SPACING)
}
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
Text("Subtasks:")
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
List {
ForEach(self.subTaskDataList, id: \.id) { sub_td in
Text(sub_td.name)
}
.onDelete(perform: self.delete)
.onMove(perform: self.move)
}
}
.navigationBarItems(trailing: EditButton())
}
...
}
It also doesn't like to be edited here (see FLAG comment):
struct TaskPlayerView: View {
#Environment(\.managedObjectContext) var managedObjectContext
var taskDataList: [TaskData] {
return self.alarmData.taskDataList
}
var taskData: TaskData {
return self.taskDataList[self.taskIdx]
}
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
#ObservedObject var alarmData: AlarmData
#State var taskIdx: Int = 0
// For the timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var isPlay: Bool = true
#State var done: Bool = false
#State var startTime: Date?
#State var lastTime: Date = Date()
#State var durationBeforePause: TimeInterval = 0
#State var durationSoFar: TimeInterval = 0
var body: some View {
VStack {
if !self.done {
...
HStack {
Spacer()
Button(action: {
withAnimation {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
// FLAG: It fails here where setting task data
self.taskData.lastDuration_ = self.durationSoFar
self.taskData.done = true
self.taskData.lastEdited = Date()
self.next()
}
}
}) {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
} else {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
.foregroundColor(Color.gray)
}
}
Spacer()
}
...
}
...
}
...
}

Resources