UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

I could really, really use some help with a concept that continues to hurt my head: Initializing.

Forums > SwiftUI

Hi Everyone,

This is a long read, but it comes from my biggest frustration and lack of knowledge in SwiftUI, and how it relates to my own project. It’s the thing about injecting un-initialized objects into views. So something like, “AccountViewMain()”, will need to have a value if there’s a variable in AccountViewMain, that’s not initialized. From what I know, every object needs to be initialized. I think I’m right about that. I’ll add my code below.

For the most part, I can make sense of this. Where I KEEP running into trouble is how this relates to core data, and more specifically some help I received from a code mentor I connected with. The mentor was great, but expensive. Christmas is coming up so I’ve got to be careful with how much I spend.

My project has several views that reference my core data persistent store. My mentor used Combine to make it so whenever a user clicks the add button, a new object is already stored in the persistent store. So that when you go back to the list that presents those different objects (in my case accounts), they all show up, even with nothing in them. If I add text to that object, it shows up. But the problem is that whenever a user just clicks to a new object(account), the app automatically creates it. Not a desired effect.

And I know why this happens. It’s because the button I click on runs this code he made. It’s a view that does not ask for injection, and I think that’s because the view my mentor created runs an initializer. This is all cool, but not entirely helpful.

I have another view that looks just like the one my mentor made, but it doesn’t do any of the adding. So I want mine to appear when the user clicks to add a new object, and then use some of the code my mentor wrote to save the new entry whenever a user clicks the save button. This is the desired effect.

Here’s where I run into issues, even though my core data persistent store gets initialized, when I try to add my own view, it keeps saying, “Missing Parameter for accountBackEnd…” So this is assuming that the view called AccountViewMain, has a line like “@ObservedObject var accountBackEnd: AccountBackEnd. For reference, AccountBackEnd is the name of my core data entity. I’ve set everything else up, so it’s all good, but like I said, I run into issues here.

When I add the appropriate objects between the braces, it seems like it just keeps asking me for more and more. Like I'll fix one, and then the next one down the line asks, until I'm at my @main view. When I add it here, it says something about needing to be initialized. I'm pretty sure there's not supposed to be one there. Right?

I’ve tried adding “*** = AccountBackEnd()” to the end of that line, but naturally that doesn’t work. I think it’s something to do with the “= xxx()” meaning that it’s recreating that view. Not referencing it. To add to this, whenever I try to look up how to initialize online, I really don’t get anywhere.

Is there someone who could please help me understand what it is that I should be doing here?

I’ll add what I think is pertinent code below. Please let me know if there is anything I’m missing here as far as code goes.

Thank you all kindly!

The view that displays my list:

import SwiftUI
import CoreData

struct DatabaseViewMain: View {

    @ObservedObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton

    var body: some View {

        VStack {

            //Creates the 'add account' button
            HStack {
                Spacer()

                NavigationLink {
                    AddAccountContainer()
                } label: {
                    SubHeaderViewIcon(subheaderIcon: "plus.square", subheaderIconColor: Color("EditButtonBlue"))
                }
                .padding()
            }

            //Creates the List view
            List {
                ForEach(refactoringAccount.savedAccounts) {account in
                    NavigationLink {
                        AccountDetailMain(accountBackEnd: account)

                    } label: {

                        DatabaseViewCell(
                            accountNameFromCell: account.accountCompanyName,
                            accountLastVisitedFromCell: account.accountCity,
                            accountTotalFromCell: account.accountLastDateVisited)

                        //Text(account.accountCompanyName)
                    }
                    .foregroundColor(.gray)
                }
                .onDelete(perform: refactoringAccount.deleteAccount)
                .onAppear(perform: refactoringAccount.fetchAccount)

            }
            .listStyle(PlainListStyle())

        }
        .navigationBarHidden(true)
    }
}

The view that my mentor created:

import SwiftUI

struct AddAccountContainer: View {

    @State var account: AccountBackEnd?

    func newAccount() -> AccountBackEnd {
        let context = RefactoringAccount.refactoringAccountSingleton.container.viewContext
        let newAccount = AccountBackEnd(context: context)
        return newAccount
    }

    var body: some View {        
        VStack() {
            if let account = self.account {
                AddAccountView(accountBackEnd: account)
            }
        }
        .onAppear {
            if account == nil { account = newAccount() }
        }
    }
}

The View that I’d like to use:

import SwiftUI
import CoreData

//View for user to add a new account with
struct AddAccountView: View {    

    @ObservedObject var accountBackEnd: AccountBackEnd

    @Environment(\.presentationMode) var presentationMode

    var body: some View {

        ScrollView {

            VStack {

                Group {
                    MainAddHeaderView(mainTextField: "Account", iconField: "building")

                    Spacer()
                        .frame(height: 5)

                    HStack {
                        NavigationLink {
                            CancelButton()
                        } label: {
                            MainHeaderViewBottomLeft()
                        }
                        Spacer()
                    }

                }

                Button(action: {
                    do {
                        try accountBackEnd.managedObjectContext?.save()

                    } catch {
                        print("There was a problem saving: \(error)")
                    }
                    presentationMode.wrappedValue.dismiss()
                    print("It exited")
                })
                {
                    LargeSaveButtonBlue(saveButtonLabel: "Save Changes")
                }

                OneHundredPointSpacer()

                //Exit
                LargeDeleteButton(deleteButtonLabel: "Delete Changes")
                OneHundredPointSpacer()
            }
            .navigationBarBackButtonHidden(true)
            .navigationBarHidden(true)
        }
    }
}

2      

@ObservedObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton

@ObservedObject should be used for objects that are not owned by the View, that are passed into the View from somewhere else. You should not be initializing an @ObservedObject in your View.

Use @StateObject in the View where the object is created/owned.

Pass the object into another View, where it becomes an @ObservedObject.

So, in very skeletal outline:

struct ParentView: View {
    @StateObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton
    //ParentView owns this object, so use StateObject

    var body: some View {
        NavigationView {
            NavigationLink("Go to Child View", destination: ChildView(account: refactoringAccount))
            //pass refactoringAccount object into ChildView
        }
    }
}

struct ChildView {
    @ObservedObject var account: RefactoringAccount
    //ChildView does not own this object, so use ObservedObject
    //you don't need to initialize account here, since it will be passed in from elsewhere

    var body: some View {
        //do something here
    }
}

2      

Ok I think that might help.

Will that stop the compiler from asking about the “Missing Parameter for accountBackEnd…” issue that I've been running into?

Thank you!

2      

Ok - So I just did the following:

In AddAccountView I added, "@StateObject var accountBackEnd = AccountBackEnd()", to replace "@ObservedObject var accountBackEnd: AccountBackEnd."

Right now it just crashed with:

Thread 1: "-[RangerCRM.AccountBackEnd accountCompanyName]: unrecognized selector sent to instance 0x6000033ac280"

Should I have added that to the view that the user first clicks on though?

2      

In AddAccountView I added, "@StateObject var accountBackEnd = AccountBackEnd()", to replace "@ObservedObject var accountBackEnd: AccountBackEnd."

OK, but why did you do that? You need to understand why things are the way they are (or should be) before just making changes. What is the flow of your data? Why do you have an AccountBackEnd created in AddAccountContainer and passed into AddAccountView? What is the purpose of these Views and the data within them? I get the sense that you don't have a clear understanding of how SwiftUI handles data flow and state and how the various property wrappers factor into it.

It's kind of hard to advise you what to do since none of us know the architecture and guts of your app. I will say that this all (meaning not just this question but all the others you have asked around your use of Core Data) seems like it is way overcomplicated. Core Data can be tricky but it's really not all that hard to set up and use.

To use one example: RefactoringAccount. It seems as if this is your persistent data controller for accessing the Core Data store. If so, why do you need to create it as an ObservedObject in your DatabaseMainView? Why aren't you getting it from the environment? Are you not using the default framework that Xcode gives you when you create a new project using Core Data? It's perfectly okay to do these things, but you need to understand why you are going that route instead of using the defaults provided.

I bring these things up not to discourage you or to berate you for doing things wrong or not understanding stuff. I bring these things up because if you don't really understand what you are doing in your code, you may fix this issue or that issue as they occur, but your code will be a ticking time bomb, just waiting for something else to go wrong and you won't know how to approach fixing that issue because you don't have a good grasp on what's been done before.

I would suggest rewatching some of Paul's videos on Core Data and comparing to your own code to see what is different and why any deviations you may have are the right/wrong thing for your particular situation. Other good sources of Core Data info are YouTube videos from azamsharp and CodeWithChris. Donny Wals has some good posts about Core Data at his blog and has written one of the best recent books on it, Practical Core Data (which happens to be on sale for the rest of this week!). A poster from these forums, @delawaremathguy, has a github repo demostrating Core Data with SwiftUI. All of these would be great resources for you to figure out what's going on in your code. And, of course, you can always ask more questions, but be aware that it can sometimes be hard to give good answers since we don't get the "big picture" of your codebase.

Good luck!

2      

Hi RoosterBoy,

Thanks for writing back. Like I've said befor, I really do appreciate your responses.

To answer your first question, I did that as I was under the impression that that view (AccountBackEnd) was first being created in DataBaseViewMain. Or at least first being referenced there. I interpreted your line, "Use @StateObject in the View where the object is created/owned.' to mean that. I gather my interpretation is incorrect.

For what it's worth I do follow a lot of videos and read a lot of articles, but clearly I have blindspots that I'm not picking up on or being exposed to. For example, a lot of times I'm not sure how to ask what it is I'm trying to figure out. Like what is my issue even called, where the compiler asks, "Missing argument for.." Honestly, I'm really trying here.

Thanks again.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.