NEW: Learn to build the incredible iOS 15 Weather app today! >>

I'm having trouble refactoring a function call

Forums > SwiftUI

Hi Everyone,

I'm trying to refactor a function to make less future errors in my code. I think I'm doing it the right way, but I keep coming across this error, "No ObservableObject of type PublishedClassAccount found. A View.environmentObject(_:) for PublishedClassAccount may be missing as an ancestor of this view."

I will put my code below, but please let me know if I've missed anything. I keep reading online that it has something to do with needing to add an EnvironmentObject, but I can't make sense of it past that. I've been trying to refactor this for weeks and still not getting anywhere. Once I've learned this I'll apply this refactor to other views that will need it.

Any help here is greatly appreciated! Thank you!

This is the class that I'm trying to refactor to. I've added all of the objects to it that needed to be. Error happens on the line that reads, "newAccount.accountCompanyName = publishedClassAccount.accountCompanyNamePublished":

import SwiftUI

class funcTest: ObservableObject {

    

    //Handles the in-out with the CoreData persistence store

    @Environment(\.managedObjectContext) var viewContext

    @FetchRequest(

        sortDescriptors: [NSSortDescriptor(keyPath: \AccountBackEnd.accountID, ascending: true)],

        animation: .default)

    var accounts: FetchedResults<AccountBackEnd>

    

    //Access Published Objects

    @EnvironmentObject var publishedClassAccount: PublishedClassAccount

    

    //Used for button/nav call out

    @Environment(\.presentationMode) var presentationMode

    

    func addAccount() {

        withAnimation {

            //Adds to persistence store

            let newAccount = AccountBackEnd(context: viewContext)

            newAccount.accountCompanyName = publishedClassAccount.accountCompanyNamePublished

            newAccount.accountAddress = publishedClassAccount.accountAddressPublished

            

            PersistenceController.shared.save

            {

                error in

                if let error = error {

                    print(error.localizedDescription)

                    return

                }

                print("Successfully saved account.")

            }

        

            //Exits  view

            self.presentationMode.wrappedValue.dismiss()

            publishedClassAccount.accountCompanyNamePublished = ""

        }

    }

}

This is the view that first allows the user to add an account:

import SwiftUI

import CoreData

//View for user to add a new account with

struct AddAccountView: View {

    

    //Handles the in-out with the CoreData persistence store

    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(

        sortDescriptors: [NSSortDescriptor(keyPath: \AccountBackEnd.accountID, ascending: true)],

        animation: .default)

    private var accounts: FetchedResults<AccountBackEnd>

    //Access Published Objects

    @EnvironmentObject var publishedClasses: PublishedClassAccount

    

    //Used for button/nav call out

    @Environment(\.presentationMode) var presentationMode

     
     //Calls func for refactoring
    @StateObject var vm = funcTest()

    var body: some View {

        

        ScrollView {

            

            VStack {

                Group {

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

                }

                

                Group{

                    AddAccountCompanyName()

                    ViewSpacer()

                    

                    AddAccountAddress()

                    ViewSpacer()

                    

                    AddAccountDetails()

                    ViewSpacer()

                    

                    

                    Button(action: vm.addAccount) {

                        LargeSaveButtonBlue(saveButtonLabel: "Save Changes")

                    }

                    .environmentObject(publishedClasses)

                    

                }

                

                ViewSpacer()

                

                LargeDeleteButton(deleteButtonLabel: "Cancel Changes")

                ViewSpacer()

                

            }

        }

        .navigationBarHidden(true)

    }

    

}

And this is the view that is meant to display the Account after it's been entered:

import SwiftUI

import CoreData

struct AccountDetailMain: View {

    //Takes object from @StateObject, presents as var

    @ObservedObject var accountBackEnd: AccountBackEnd

    

    var body: some View {

        

        ScrollView {

            

            VStack {

                MainHeaderViewTop(mainTextField: accountBackEnd.accountCompanyName ?? "", iconField: "building")

            }

            

            Spacer()

                .frame(height: 5)

            

            HStack {

                

                NavigationLink {

                    EditAccountView()

                } label: {

                    MainHeaderViewBottomLeft()

                }

                Spacer()

                

                NavigationLink {

                    AddAccountView()

                } label: {

                    SubHeaderViewIcon(subheaderIcon: "plus.square", subheaderIconColor: Color("EditButtonBlue"))

                }

            }

            

            ViewSpacer()

            

            Group {

                SalesRecords()

                ViewSpacer()

                

                LatestThreeQuotes()

                ViewSpacer()

                

                LatestNote()

                ViewSpacer()

            }

            

            Group {

                AccountsContactsView()

                ViewSpacer()

                

                BranchContactInfoView()

                ViewSpacer()

                

                AccountAddressView()

                ViewSpacer()

                

                RepDetailsView()

                ViewSpacer()

            }

            

            

            Group {

                

                NavigationLink {

                    EditAccountView()

                } label: {

                    LargeEditButtonGreen(editButtonLabel: "Edit Account")

                }

                ViewSpacer()

                

            }

        }

        .navigationBarHidden(true)

        //.environmentObject(publishedClasses)

    }

}

   

You cannot have @Environment (or @EnvironmentObject) and @FetchRequest in an ObservableObject. Those property wrappers rely on a View's environment to function so they will only work inside a View.

   

Thanks for the reply.

So if I'm reading you correctly, there's no way to refactor my function out?

Or how do I fix this?

   

Random thought: Would I be better off using a func for my fetch request, rathen than a property wrapper?

It seems like I'm running into errors with the property wrapper way.

   

When you want to use a FetchRequest in your view model you can still switch to the old method of creating a FetchRequest. Personally, when my views get more complicated and I defintitely want a view model for handling all the business logic I do that all the time. Just because there is a property wrapper doesn't mean I have to use it.

I just can repeat myself: don't stick to patterns just because but adjust the given patterns to your needs and your specific use case.

   

In May 20212, I wrote a post about hiding the details of your fetch request by using a Core Data feature of putting fetch requests into the XCData Model.

https://www.hackingwithswift.com/forums/swift/core-data-fetch-requests-templates-in-the-xcdatamodeld-how-to-substitute-values-for-placeholders/7989

Not sure of this helps, but I seldom see this feature mentioned. I think it helps hide the details, so you focus on business rules.

Also, please be kind to us reviewers! Please edit your code sample and TAKE OUT all the unnecessary blank lines! T H A N K S.

   

Hi @Hatsushira and @Obelix,

Thank you both for taking the time to write back!

@Hatsushira - I agre that patterns might not always be neccessary, but for what I'm trying to do, I think it makes the most sense. Basically I have four different CoreData entities, and each of those contains anywhere from 5-30 different atributes. So right now, I have my 'addAccount' function, in the same file that I'm using for a pretty main view. I think ideally I'd like to refactor to keep things tidier and to avoid future issues.

@Obelix - First, very sorry for the unneccessary extra code. I was told on another forum to add as much as needed, although, clearly it's not all needed. I will work to fix that.

Second, I really appreciate the article you wrote, and further, the article that Paul wrote. I'm pretty sure I get what you're going for but I'm not sure it's really what I need. I could be wrong here - I often am.

What I'm really aiming to do is to refactor the function code from my views. I will end up with several views/files, that will all have replicated code. I really don't want that. My complexity comes from using swiftUI with Core Data, and property wrappers.

What I'd rather have is my views access functions in other files by passing in those functions. For whatever reason though, I csn't for the life of me make that happen right now.

Do you happen to have any insights to how this might be possible?

Thank you both again! Cheers!!!

   

I ended up re-writing my refactor file to use functions in place of property wrappers. After reading your reply and reading more about Observable Objects, I finally got passed this error: Thread 1: Fatal error: No ObservableObject of type RefactoringAccount found. A View.environmentObject(_:) for RefactoringAccount may be missing as an ancestor of this view. Turns out I neglected to add ".environmentObject(refactorAccount)" in the first file that loads. I can't remember what it's called, but it's got @main near its beginning. Also, and you'll be happy to know this, my refactor file now only has @Published calls in it. ;) Woot! And now I'm dealing with getting data to store! LOL! The pain never ends. Thanks again!

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS, you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Learn More

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

Reply to this topic…

You need to create an account or log in to reply.

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.