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

Solved: SwiftUI/EnvironmentObject.swift:90: Fatal error: No ObservableObject of type PomodoroModel found. A View.environmentObject(_:) for PomodoroModel may be missing as an ancestor of this view.

Forums > Swift

The debugger says: SwiftUI/EnvironmentObject.swift:90: Fatal error: No ObservableObject of type PomodoroModel found. A View.environmentObject(_:) for PomodoroModel may be missing as an ancestor of this view.

I am not sure why? I assume I have completed the tutorial correctly: https://www.youtube.com/watch?v=Pd90OTQiOaA

I have named the Home file PomodoroHome.

My PomodoroHome.swift file's code:

import SwiftUI

struct PomodoroHome: View {
    @EnvironmentObject var pomodoroModel: PomodoroModel
    var body: some View {
        VStack {
            Text("Focus Timer")
                .font(.title2.bold())

            GeometryReader{proxy in
                VStack(spacing: 15){
                    // MARK: Timer Ring
                    ZStack{
                        Circle()
                            .fill(.whiteBlack.opacity(0.03))
                            .padding(-40)

                        Circle()
                            .trim(from: 0, to:pomodoroModel.progress)
                            .stroke(.white.opacity(0.03),lineWidth: 80)
                            .padding(-40)

                        // MARK: Shadow
                        Circle()
                            .stroke(Color("whiteBlack"),lineWidth: 5)
                            .blur(radius: 15)
                            .padding(-2)

                        Circle()
                            .fill(Color("blackWhite"))

                        Circle()
                            .trim(from: 0, to: pomodoroModel.progress)
                            .stroke(Color("whiteBlack").opacity(0.7),lineWidth: 10)

                        // MARK: Knob
                        GeometryReader{proxy in
                            let size = proxy.size

                            Circle()
                                .fill(Color("whiteBlack"))
                                .frame(width: 30, height: 30)
                                .overlay(content: {
                                    Circle()
                                        .fill(.white)
                                        .padding(5)
                                })
                                .frame(width: size.width, height: size.height, alignment: .center)
                            // MARK: Since view is rotated thats why using X
                                .offset(x: size.height / 2)
                                .rotationEffect(.init(degrees: pomodoroModel.progress * 360))
                        }

                        Text(pomodoroModel.timerStringValue)    .font(.system(size: 45, weight: .light))
                            .rotationEffect(.init(degrees: -90))
                            .animation(.none, value: pomodoroModel.progress)
                    }
                    .padding(60)
                    .frame(height: proxy.size.width)
                    .rotationEffect(.init(degrees: -90))
                    .animation(.easeInOut, value: pomodoroModel.progress)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)

                    Button {
                        if pomodoroModel.isStarted{
                            pomodoroModel.stopTimer()
                        }else{
                            pomodoroModel.addNewTimer = true
                        }
                    } label: {
                        Image(systemName: !pomodoroModel.isStarted ? "timer" : "pause")
                            .font(.largeTitle.bold())
                            .foregroundColor(.black)
                            .frame(width: 80, height: 80)
                            .background{
                                Circle()
                                    .fill(Color("whiteBlack").opacity(0.03))
                            }
                            .shadow(color: Color("whiteBlack").opacity(0.6), radius: 8, x: 0, y: 0)
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
            }
        }
        .padding()
        // .background{
        //    Color("whiteBlack")
        // }
        //.preferredColorScheme(.dark)
        .overlay(content: {
            ZStack{
                Color.black
                    .opacity(pomodoroModel.addNewTimer ? 0.25 : 0)
                    .onTapGesture {
                        pomodoroModel.hour = 0
                        pomodoroModel.minutes = 0
                        pomodoroModel.seconds = 0
                        pomodoroModel.addNewTimer = false
                    }

                NewTimerView()
                    .frame(maxHeight: .infinity,alignment: .bottom)
                    .offset(y: pomodoroModel.addNewTimer ? 0 : 400)
            }
            .animation(.easeInOut, value: pomodoroModel.addNewTimer)
        })
        .onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) {
            _ in
            if pomodoroModel.isStarted{
                pomodoroModel.updateTimer()
            }
        }
    }

    // MARK: New Timer Bottom Sheet
    @ViewBuilder
    func NewTimerView()->some View{
        VStack(spacing: 15){
            Text("Add New Timer")
                .font(.title2.bold())
                .padding(.top,10)

            HStack(spacing: 15){
                Text("\(pomodoroModel.hour) hr")
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.black.opacity(0.3))
                    .padding(.horizontal,20)
                    .padding(.vertical,12)
                    .background{
                        Capsule()
                            .fill(.whiteBlack.opacity(0.07))
                    }
                    .contextMenu{
                        ContextMenuOptions(maxValue: 12, hint: "hr") { value in
                            pomodoroModel.hour = value
                        }
                    }

                Text("\(pomodoroModel.minutes) min")
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.black.opacity(0.3))
                    .padding(.horizontal,20)
                    .padding(.vertical,12)
                    .background{
                        Capsule()
                            .fill(.whiteBlack.opacity(0.07))
                    }
                    .contextMenu{
                        ContextMenuOptions(maxValue: 60, hint: "min") { value in
                            pomodoroModel.minutes = value
                        }
                    }

                Text("\(pomodoroModel.seconds) sec")
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.black.opacity(0.3))
                    .padding(.horizontal,20)
                    .padding(.vertical,12)
                    .background{
                        Capsule()
                            .fill(.whiteBlack.opacity(0.07))
                    }
                    .contextMenu{
                        ContextMenuOptions(maxValue: 60, hint: "sec") { value in
                            pomodoroModel.seconds = value
                        }
                    }

            }
            .padding(.top,20)

            Button {
                pomodoroModel.startTimer()
            } label: {
                Text("Save")
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.white)
                    .frame(height:60)
                    .padding(.horizontal,100)
                    .background{
                        Capsule()
                            .fill(Color("whiteBlack"))
                    }
            }
            .disabled(pomodoroModel.seconds == 0)
            .opacity(pomodoroModel.seconds == 0 ? 0.5 : 1)
            .padding(.top)
        }
        .padding()
        .frame(maxWidth: .infinity)
        .background{
            RoundedRectangle(cornerRadius: 10, style: .continuous)
                .fill(Color("blackWhite"))
                .ignoresSafeArea()
        }
    }

    // MARK: Reusable Context Menu Options
    @ViewBuilder
    func ContextMenuOptions(maxValue: Int,hint: String,onClick: @escaping (Int)->())->some View{
        ForEach(0...maxValue,id: \.self){value in
            Button("\(value) \(hint)"){
                onClick(value)
            }
        }
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        FocusView()
            .environmentObject(PomodoroModel())
    }
}

I assume there is a problem with line 4 but I am not sure how to solve the problem. The fatal error repeats itself later in the file.

The PomodoroModel.swift (under View folder):

import SwiftUI

class PomodoroModel: NSObject,ObservableObject {

     // MARK: Timer Properties
    @Published var progress: CGFloat = 1
    @Published var timerStringValue: String = "00:00"
    @Published var isStarted: Bool = false
    @Published var addNewTimer: Bool = false

    @Published var hour: Int = 0
    @Published var minutes: Int = 0
    @Published var seconds: Int = 0

    // MARK: Total Seconds
    @Published var totalSeconds: Int = 0
    @Published var staticTotalSeconds: Int = 0

    // MARK: Starting Timer
    func startTimer(){
        withAnimation(.easeInOut(duration: 0.25)){isStarted = true}
        // MARK: Setting String Time Value
        timerStringValue = "\(hour == 0 ? "" : "\(hour):")\(minutes >= 10 ? "\(minutes)":"0\(minutes)")\(seconds >= 10 ? "\(seconds)":"0\(seconds)")"
        // MARK: Calculating Total Seconds for Timer Animation
        totalSeconds = (hour * 3600) + (minutes)
    }

    // MARK: Stopping Timer
    func stopTimer(){

    }

    // MARK: Updating Timer
    func updateTimer(){

    }
}

The PomodoroTimerApp.swift:

import SwiftUI

struct PomodoroTimerApp: App {
    // MARK: Since we're doing background fetching initialising here
    @StateObject var pomodoroModel: PomodoroModel = .init()
    var body: some Scene {
        WindowGroup {
            FocusView()
                .environmentObject(pomodoroModel)
        }
    }
}

FocusView.swift (I renamed it from ContentView.swift as that was separate):

import SwiftUI

struct FocusView: View {
    @EnvironmentObject var pomodoroModel: PomodoroModel
    var body: some View {
        PomodoroHome()
    }
}

struct FocusView_Previews: PreviewProvider {
    static var previews: some View {
        FocusView()
    }
}
// MARK: this is ContentView.swift

I thought the app was initialised in PomodoroTimerApp.swift though? Please help. Anything would be much appreciated.

2      

I think you just forgot to add observableObject to Focus preview:

struct FocusView_Previews: PreviewProvider {
    static var previews: some View {
        FocusView()
            .environmentObject(PomodoroModel())
    }
}

2      

Thanks for your response! I have made those changes but it still does not run? I have posted this to StackOverflow as well and a drive containing my Xcode project is attached in the comments as a gDrive link: StackOverflow Link

2      

I have added a video of the error link

2      

Works now! Turns out, I had two @mains with apps.

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!

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.