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.