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

SOLVED: It works on iPhone, but not on iPad

Forums > Swift

Why does this works perfectly fine on iPhone but crashes on ipad

import SwiftUI
import MessageUI

struct ContactDetailView: View {
    let contact: ContactEntity

    @EnvironmentObject var vm: CoreDataViewModel

    @State private var showCompanyConnection = false

    @State private var showJobConnection = false
    @State private var showingAddJobScreen = false
    @State private var currentSelectedJob: Int = 0

    @State var result: Result<MFMailComposeResult, Error>?
    @State var msgBody: String?
    @State private var isShowingMailView = false

    var body: some View {
        VStack {
            ContactHeaderComponent(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "")
                .frame(height: 50)
            NavigationLink {
                ContactDetailView(contact: contact)
            } label: {
                Text(contact.company?.companyName ?? "")
                    .font(.callout)
                    .foregroundColor(.secondary)
            }
            Spacer()

            List {                
                // MARK: Contact Information
                Section {
                    HStack {
                        Image(systemName: "phone")
                        Spacer()
                        if (contact.phoneNumber != "") || (contact.phoneNumber != nil) {
                            let numberString = contact.phoneNumber!
                            Button {
                                let telephone = "tel://"
                                let formattedString = telephone + numberString
                                guard let url = URL(string: formattedString) else { return }
                                UIApplication.shared.open(url)
                            } label: {
                                Text(numberString)
                            }
                        } else {
                            Text("")
                        }
                    }
                    HStack {
                        Image(systemName: "envelope")
                        Spacer()
                        Button {
                            isShowingMailView.toggle()
                        } label: {
                            Text(contact.emailAdress ?? "")
                        }
                    }
                }

                // MARK: Add Job
                Section(header: Text(StringConstants.addJobs)) {
                    Toggle(StringConstants.addConnectionJobC, isOn: $showJobConnection)

                    if showJobConnection {
                        HStack {
                            Picker(selection: $currentSelectedJob, label: Text(StringConstants.selectedJobc)) {
                                ForEach(0 ..< vm.jobs.count, id: \.self) {
                                    Text(LocalizedStringKey((self.vm.jobs[$0].jobTitle ?? "") + " at " +  (self.vm.jobs[$0].company?.companyName ?? "")))
                                }
                            }
                        }
                        Button {
                            showingAddJobScreen.toggle()
                        } label: {
                            Label(StringConstants.addNewJobC, systemImage: "plus.circle")
                        }

                        Button {
                            if vm.contacts.firstIndex(of: contact) != nil {
                                vm.addJobToContact(contactIndex: vm.contacts.firstIndex(of: contact)!, jobIndex: currentSelectedJob)
                            }
                            showJobConnection.toggle()
                        } label: {
                            Text(StringConstants.saveConnection)
                        }
                    }
                }

                // MARK: Exisiting Jobs
                Section(header: Text(StringConstants.jobsC)) {
                    if let jobs = contact.jobs?.allObjects as? [JobEntity] {
                        ForEach(jobs) { job in
                            NavigationLink {
                                JobDetailView(job: job)
                            } label: {
                                VStack(alignment: .leading) {
                                    Text(job.jobTitle ?? "")
                                    Text(StringConstants.status + ": " + (job.status ?? ""))
                                        .font(.caption)
                                }
                            }
                        }
                    }
                }
            }
        }
        .toolbar(content: {
            ToolbarItem(placement: .navigationBarTrailing) {
                NavigationLink {
                    if vm.contacts.firstIndex(of: contact) != nil {
                        ContactDetailEditView(contactIndex: vm.contacts.firstIndex(of: contact)!, model: contact)
                            .environmentObject(vm)
                    }
                } label: {
                    Text(StringConstants.edit)
                }
            }
        })
        .sheet(isPresented: $showingAddJobScreen) {
            AddJobView()
        }
        .sheet(isPresented: $isShowingMailView) {
            MailView(result: self.$result, recipients: contact.emailAdress ?? "")
        }
    }
}

ERROR: Thread 1: Fatal error: No ObservableObject of type CoreDataViewModel found. A View.environmentObject(_:) for CoreDataViewModel may be missing as an ancestor of this view.

On vm in (all the way down)

if vm.contacts.firstIndex(of: contact) != nil {
                        ContactDetailEditView(contactIndex: vm.contacts.firstIndex(of: contact)!, model: contact)
                            .environmentObject(vm)
                    }

I have to mention that the direct parent view on iPhone is a navigationView, on iPad it isn't (it uses a sie bar and on the grand-parent)

2      

For iPad perhaps you have to pass the CoreDataViewModel further up the view hierarchy in your environment.

2      

The CoreDataViewModel is passed in on the top level with:

.environmentObject(vm)

and that with every level below.

If i change it to this for the ipad:

private var iPadView: some View {
        VStack {
            ContactHeaderComponent(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "")
                .frame(height: 50)
            NavigationLink {
                ContactDetailView(contact: contact)
            } label: {
                Text(contact.company?.companyName ?? "")
                    .font(.callout)
                    .foregroundColor(.secondary)
            }
            Spacer()

            List {
                // MARK: Contact Information
                Section {
                    HStack {
                        Image(systemName: "phone")
                        Spacer()
                        if (contact.phoneNumber != "") || (contact.phoneNumber != nil) {
                            let numberString = contact.phoneNumber!
                            Button {
                                let telephone = "tel://"
                                let formattedString = telephone + numberString
                                guard let url = URL(string: formattedString) else { return }
                                UIApplication.shared.open(url)
                            } label: {
                                Text(numberString)
                            }
                        } else {
                            Text("")
                        }
                    }
                    HStack {
                        Image(systemName: "envelope")
                        Spacer()
                        Button {
                            isShowingMailView.toggle()
                        } label: {
                            Text(contact.emailAdress ?? "")
                        }
                    }
                }

                // MARK: Add Job
                Section(header: Text(StringConstants.addJobs)) {
                    Toggle(StringConstants.addConnectionJobC, isOn: $showJobConnection)

                    if showJobConnection {
                        HStack {
                            Picker(selection: $currentSelectedJob, label: Text(StringConstants.selectedJobc)) {
                                ForEach(0 ..< vm.jobs.count, id: \.self) {
                                    Text(LocalizedStringKey((self.vm.jobs[$0].jobTitle ?? "") + " at " +  (self.vm.jobs[$0].company?.companyName ?? "")))
                                }
                            }
                        }
                        Button {
                            showingAddJobScreen.toggle()
                        } label: {
                            Label(StringConstants.addNewJobC, systemImage: "plus.circle")
                        }

                        Button {
                            if vm.contacts.firstIndex(of: contact) != nil {
                                vm.addJobToContact(contactIndex: vm.contacts.firstIndex(of: contact)!, jobIndex: currentSelectedJob)
                            }
                            showJobConnection.toggle()
                        } label: {
                            Text(StringConstants.saveConnection)
                        }
                    }
                }

                // MARK: Exisiting Jobs
                Section(header: Text(StringConstants.jobsC)) {
                    if let jobs = contact.jobs?.allObjects as? [JobEntity] {
                        ForEach(jobs) { job in
                            NavigationLink {
                                JobDetailView(job: job)
                            } label: {
                                VStack(alignment: .leading) {
                                    Text(job.jobTitle ?? "")
                                    Text(StringConstants.status + ": " + (job.status ?? ""))
                                        .font(.caption)
                                }
                            }
                        }
                    }
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    showingContactEditView.toggle()
                } label: {
                    Text(StringConstants.edit)
                }
            }
        }
        .sheet(isPresented: $showingAddJobScreen) {
            AddJobView()
        }
        .sheet(isPresented: $isShowingMailView) {
            MailView(result: self.$result, recipients: contact.emailAdress ?? "")
        }
        .sheet(isPresented: $showingContactEditView) {
            if vm.contacts.firstIndex(of: contact) != nil {
                ContactDetailEditView(contactIndex: vm.contacts.firstIndex(of: contact)!, model: contact)
                    .environmentObject(vm)
            }
        }
    }

It shows the ContactDetailView, but when you hit the edit button the sheet isn't presented. It crashes and gives the following error: "Thread 1: Fatal error: No ObservableObject of type CoreDataViewModel found. A View.environmentObject(_:) for CoreDataViewModel may be missing as an ancestor of this view."

2      

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.