TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED! Having trouble updating picker value when passing data through with onAppear & Core Data

Forums > SwiftUI

Hello Hacking With Swift,

I'm building a Edit View, that allows a user to make updates to a list item and save it back to core data and the list. To do this I am passing in the values from the list in the appropriate fields using the .onAppear action.

All data is passed in appropriately, and the toggle and text fields allow me to make changes easily and save them back to core data and update the list item. However, if I try to change the picker value (emojiChoice) and select a new one, the picker value does not change or save back to Core Data.

When I comment out the onAppear action for the picker variable (emojiChoice), the picker now allows me to choose the value I want and save it back to Core Data. I also have another view that allows a user to create items that is near identical to this Edit View, minus the onAppear action, that works as well. The picker allows a user to choose and emoji. Because there are alot of emoji's, I created an array and instead am just passing the array index of a selected emoji as an Int.

How can I fix this to allow the picker to show the value to be edited and also allow it to be changed?

Here's the code of the Edit View:

import CoreData
import SwiftUI

struct editRemindr: View {
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode

    let emojiList = EmojiList()
    @ObservedObject var reminder: ReminderEntity
    @State private var showingDeleteAlert = false

    // Form Variables
    @State var notifyOn = true
    @State var emojiChoice = 0
    @State var notification: String
    @State var notes: String

    // Delete Reminder Function
    func deleteAction() {
        moc.delete(reminder)
        // try? self.moc.save()
        presentationMode.wrappedValue.dismiss()
    }

    // View Controller
    var body: some View {
        Form {

            // On/Off Toggle
            Toggle(isOn: $notifyOn) {
                Text("On/Off")
            }

            // Emoji Picker
            Picker(selection: $emojiChoice, label: Text("Emoji")) {
                ForEach(0 ..< emojiList.emojis.count) {
                    Text(self.emojiList.emojis[$0])
                }
            }

            // Notification Text
            Section(header: Text("NOTIFICATION")) {
                HStack {
                    TextField("Write your notification...", text: $notification)
                        .onReceive(notification.publisher.collect()) {
                            self.notification = String($0.prefix(60)) // <---- SET CHARACTER LIMIT
                    }
                    Text("\(notification.count) / 60")
                        .font(.caption)
                        .foregroundColor(.gray)
                }
            }

            // Notes Text
            Section(header: Text("NOTES")) {
                VStack {
                    MultiLineTextField(text: $notes).frame(numLines: 6)
                        .padding(.top, 5)
                        .onReceive(notes.publisher.collect()) {
                            self.notes = String($0.prefix(240)) // <---- SET CHARACTER LIMIT
                    }
                    Text("\(notes.count) / 240")
                        .font(.caption)
                        .foregroundColor(.gray)
                        .frame(maxWidth: .infinity, alignment: .trailing)
                }
            }

            // Save Changes Button
            Section() {
                Button (action: {
                    self.reminder.dateCreated = Date()
                    self.reminder.notifyOn = self.notifyOn
                    self.reminder.emojiChoice = Int64(self.emojiChoice)
                    self.reminder.notification = self.notification
                    self.reminder.notes = self.notes

                    try? self.moc.save()

                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("SAVE CHANGES")
                        .fontWeight(.bold)
                        .foregroundColor(.white)
                        .font(.body)
                }        .padding()
                    .frame(maxWidth: .infinity)
                    .background(Color.green)
                    .padding(.vertical, -6)
                    .padding(.horizontal, -15)

                // Cancel Button
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                        .frame(maxWidth: .infinity)
                }
            }
        }

        // Make List Items Appear in Fields
        .onAppear(perform: {
            self.notifyOn = self.reminder.notifyOn
            self.emojiChoice = Int(self.reminder.emojiChoice)
            self.notification = self.reminder.notification ?? "unknown"
            self.notes = self.reminder.notes ?? "unknown"
        }) 
            .alert(isPresented: $showingDeleteAlert) {
                Alert(title: Text("Delete Reminder"), message: Text("Are you sure you want to delete this Reminder?"), primaryButton: .destructive(Text("Delete")) {
                    self.deleteAction()
                    }, secondaryButton: .cancel()
                )
        }
        .navigationBarTitle("Edit Reminder")
        .navigationBarItems(trailing: Button(action: {self.showingDeleteAlert = true
        }) {
            Image(systemName: "trash")

        })
    } 

    // Creates Text Limits
    class TextLimit: ObservableObject {
        @Published var text = "" {
            didSet {
                if text.count > characterLimit && oldValue.count <= characterLimit {
                    text = oldValue
                }
            }
        }
        let characterLimit: Int

        init(limit: Int = 5){
            characterLimit = limit
        }

    }

}

// MARK: - PREVIEW GENERATOR
struct editRemindr_Previews: PreviewProvider {
    static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

    static var previews: some View {
        let reminder = ReminderEntity(context: moc)
        reminder.notifyOn = true
        reminder.emojiChoice = 3
        reminder.notification = "Test Notification"
        reminder.notes = "Test Notification Notes"

        return NavigationView {
            editRemindr(reminder: reminder, notification: "Test Notification", notes: "Test Notification Notes")
        }
    }
}

Here's the code for the Content View with the list if it helps as well.

import SwiftUI
import CoreData

struct ContentView: View {

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: ReminderEntity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ReminderEntity.dateCreated, ascending: false)])
    var reminder: FetchedResults<ReminderEntity>

    @State private var showingAddScreen = false
    @State var showWelcomeScreen = false

    let emojiList = EmojiList()

    //Toggle Control
    @State var notifyOn = true

    // Save Items Function
    func saveItems() {
        do {
            try moc.save()
        } catch {
            print(error)
        }
    }

    // Delete Item Function
    func deleteItem(indexSet: IndexSet) {
        let source = indexSet.first!
        let listItem = reminder[source]
        moc.delete(listItem)
    }

    // View Controller
    var body: some View {
        VStack {
            NavigationView {
                ZStack (alignment: .top) {

                    // List View
                    List {
                        ForEach(reminder, id: \.self) { notification in
                            NavigationLink(destination: editRemindr(reminder: notification, notification: notification.notification ?? "unknown", notes: notification.notes ?? "unknown")) {
                                // Text within List View
                                HStack {
                                    // MARK: TODO
                                    // Toggle("NotifyOn", isOn: true)
                                    // .labelsHidden() // Hides the label/title
                                    Text("\(self.emojiList.emojis[Int(notification.emojiChoice)]) \(notification.notification!)")
                                } 
                            }
                        } 
                        .onDelete(perform: deleteItem)
                    }

                        // Navigation Items
                        .navigationBarTitle("", displayMode: .inline)
                        .navigationBarItems(
                            leading:
                            HStack {

                                Button(action: {
                                    self.showWelcomeScreen.toggle()
                                }) {
                                    Image(systemName: "info.circle.fill")
                                        .font(.system(size: 24, weight: .regular))
                                }.foregroundColor(.gray)

                                // Positioning Remindr Logo on Navigation
                                Image("remindrLogoSmall")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    //.frame(width: 60, height: 60, alignment: .center)
                                    .padding(.leading, 83)
                                    .padding(.top, -10)
                            },

                            // Global Settings Navigation Item
                            trailing: NavigationLink(destination: globalSettings()){
                                Image("settings")
                                    .font(Font.title.weight(.ultraLight))
                            }.foregroundColor(.gray)
                    )

                    // Add New Reminder Button
                    VStack {
                        Spacer()
                        Button(action: { self.showingAddScreen.toggle()
                        }) {
                            Image("addButton")
                                .renderingMode(.original)
                        }
                        .sheet(isPresented: $showingAddScreen) {

                            newRemindr().environment(\.managedObjectContext, self.moc)

                        } 
                    }
                }
            }  .sheet(isPresented: $showWelcomeScreen) {
                welcomeScreen()
            }
        }
    }
}

// MARK: - PREVIEW GENERATOR
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Any help would be greatly appreciated! Thank you so much!

2      

Solved! I created a custom binding for the picker values and it has seemed to do the trick. Thanks to Reddit user End3r117 for pointing me in the right direction!

// View Controller
var body: some View {

    let reminderChoice = Binding(
        get: { Int(self.reminder.emojiChoice) },
        set: { self.reminder.emojiChoice = Int64($0) }
    )
    return
        Form {

            // On/Off Toggle
            Toggle(isOn: $notifyOn) {
                Text("On/Off")
            }

            // Emoji Picker
            Picker(selection: reminderChoice, label: Text("Emoji")) {
                ForEach(0 ..< self.emojiList.emojis.count, id: \.self) {
                    Text(self.emojiList.emojis[$0])
                }
            }

2      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.