Learn Courses My Dashboard

View onAppear is not being triggered

Hi, I am trying to replicate a similar project as the learning app project that is taught in module 5. I am building the app as I go through the lessons, its just my way of learning so that I remember what is being taught.

Currently I am at Lesson 5 of the module and just like LessonsListView on the application thats being built on module 5, my application has a SurahListView. This basically has a ScrollView that contains a list of Navigation Links for Surahs (just the same as lessons on the learning app), on clicking the navigation link the destination is pointed to SurahDetailView() and it contains an onAppear modifier, and this modifier performs a function that basically records the Surah the user has started reading just the same way the learning application records the lesson user started, so that upon loading SurahDetailView its content can be dynamically loaded. However when a Surah Navigation Link is clicked the onAppear does not get triggered. I have tried putting breaking points within the function and it never gets hit thus I have a bad feeling that onAppear is not at all called, I am a bit clueless of what to fix to have the onAppear called.

Source for SurahListView:

import SwiftUI

struct SurahListView: View {
    @EnvironmentObject var AppViewModel: AppViewModel
    
    
    var body: some View {
        ScrollView {
            ForEach(AppViewModel.translationData.records){surah in
                NavigationLink {
                    SurahDetailView()
                        .onAppear {
                            AppViewModel.beginReadingSurah(SurahIndex: surah.id)
                        }
                } label: {
                    ZStack {
                        GeometryReader { geo in
                            Rectangle()
                                .foregroundColor(surah.id % 2 == 0 ? .white: AppViewModel.bgOddCardColor)
                                .frame(height: geo.size.height+10)
                                .shadow(radius: 0.5)
                        }
                        LazyVStack (alignment: .leading) {
                            HStack{
                                ZStack{
                                    Image(systemName: "circle.fill")
                                        .resizable()
                                        .frame(width: 42, height: 45)
                                    Text(String(surah.id))
                                        .foregroundColor(.white)
                                        .font(Font.custom("Optima Bold", size: 20))
                                }
                                .padding(.horizontal, 20)
                                
                                
                                VStack (alignment: .leading){
                                    Text("\(surah.name)")
                                        .font(Font.custom("DecoType Naskh Regular", size: 20))
                                    Text("(\(surah.translation))")
                                        .font(Font.custom("Optima Bold", size: 18))
                                    Text("Number of Verses: \(String(surah.total_verses))")
                                        .font(Font.custom("Optima Regular", size: 12))
                                }
                            }.padding(.horizontal)
                        }.padding()
                    }
                }
                .accentColor(.black)
            }
        }
        .navigationTitle("Select \(AppViewModel.translationData.module_name)")
    }
}

Source for SurahDetailView: This is the destination view when once a NavLink on SurahListView is clicked.

import SwiftUI

struct SurahDetailView: View {
    @EnvironmentObject var AppViewModel: AppViewModel
    
    var body: some View {
        if AppViewModel.cSelectSurah != nil {
            ScrollView{
                ForEach(0..<AppViewModel.cSelectSurah!.verses.count) { index in
                    
                    ZStack {
                        GeometryReader { geo in
                            // rgb(207, 216, 220)
                            Rectangle()
                                .foregroundColor(index % 2 == 0 ? .white: AppViewModel.bgOddCardColor)
                                .frame(height: geo.size.height+10)
                                .shadow(radius: 0.5)
                        }
                        LazyVStack(alignment:.trailing, spacing: 10) {
                            HStack{
                                VStack(alignment:.trailing, spacing: 10){
                                    Text(AppViewModel.cSelectSurah!.verses[index].text)
                                        .font(Font.custom("DecoType Naskh Regular", size: 15))
                                        .multilineTextAlignment(.trailing)
                                    Text(AppViewModel.cSelectSurah!.verses[index].translation)
                                        .font(Font.custom("Optima Regular", size: 15))
                                        .multilineTextAlignment(.leading)
                                    Text(AppViewModel.cSelectSurah!.verses[index].dv_translation)
                                        .font(Font.system(size: 15))
                                        .multilineTextAlignment(.trailing)
                                }
                                
                                ZStack{
                                    Image(systemName: "circle.fill")
                                        .resizable()
                                        .frame(width: 42, height: 45)
                                    Text(String(AppViewModel.cSelectSurah!.verses[index].id))
                                        .foregroundColor(.white)
                                        .font(Font.custom("Optima Bold", size: 20))
                                }
                                .padding(.horizontal, 20)
                                
                            }
                        }
                        .padding()
                    }
                    .padding()
                    
                    
                }
                
            }
            .navigationTitle(AppViewModel.cSelectSurah!.name)
        }
        
        
    }
}

View Model that contains the function that is being performed within onAppear:

import SwiftUI
import Foundation

class AppViewModel: ObservableObject {
    // MARK: - Globaly Shared Application Data
    var app_data = [AppData]()
    @Published var duasData:Duas
    @Published var translationData:Translation
    
    // MARK: - Common Element Properties
    @Published var bgOddCardColor = Color(.sRGB, red: 207/255, green: 216/255, blue: 220/255, opacity: 1)
    
    // MARK: - Current Selected Surah
    @Published var cSelectSurah:Surah?
    var scIndex:Int = 0
    
    init() {
        self.app_data = DataService.getData()
        
        self.duasData = self.app_data[0].duas
        self.translationData = self.app_data[0].translation
    }
    
    // MARK: - Surah Navigation Controls
    func beginReadingSurah(SurahIndex sIndex:Int){
        // Find the module ID of the current Selected Surah
        for index in 0..<translationData.records.count{
            if sIndex == translationData.records[index].id {
                self.scIndex = sIndex
                break
            }
        }
        
        
        // Set the current Selected Surah
        cSelectSurah = translationData.records[scIndex]
    }
}

Here is the GitRepo in case you would like to go through the whole project:

Any assistance in resolving the issue would be really appreciated.

This must have been the weiredest fix I had gone through, doing debugging and checking I just mistakenly happen to add the else statement for the if statement on SurahDetailView()

if AppViewModel.cSelectSurah != nil {
            ScrollView{...}
} else {
            Text("Nothing")
}

And the initial issue I had is resolved but now I am getting Fatal error: Index out of range for some of the Surahs for the ForEach loop on SurahDetailView(), which is pretty weired too since this shouldn’t show an error unless the array is not properly loaded at onAppear.

Is there a fix for it though?

Solved the index out of range issue too, I changed the ForEach loop within SurahDetailView() to use elements instead of a range:

ForEach(0..<AppViewModel.cSelectSurah!.verses.count) { ... }

to

ForEach(AppViewModel.cSelectSurah!.verses) { ... }

I literally do not understand why Swift would complaint regarding the above, at the end of the day they both do the same. Anyway probably someone else could shed some lights to it.

i think the difference here is that in the first you are simply looping thru the numbers from the verses array’s count and subscription the element from the array. Kinda like :
ayaat[1]
ayaat[2]
and so on …

The issue is that when the app loads there might be a small-time where the array is not populated which means you are subscription a higher index than the array

@Abdul_Wahid thank you for the input, what you said actually makes sense cos the index out of range occurs only when once it tries to loop through lengthy arrays.

I am still stunned and scratching my head around at why swift wanted me to provide an else statement as mentioned on my initial issue, it has been resolved doing as I said but I dont really seem to understand why, cos I do not really see when the else statement would be executed within the workflow.

That might have something to do with the fact that body is a computed property, which requires a return type of some view.

Think of it like this


var name: String {
    if name == "John" {
        return "John"
    } else {
        return "Name NOt John"
    }
}

If u don’t specify the else block, there is a chance that name could not have a value

This video does a good job of explaining:
https://learn.codewithchris.com/courses/take/swiftui-views-specialist/lessons/31649424-swift-language-changes

Got it @Abdul_Wahid, thank you a lot :slightly_smiling_face:

1 Like

Just so that anyone who followed my path in completing the module and faced the stated issues on this post, actually chris addresses all these on different upcoming lessons of the module that were coming along just as @Abdul_Wahid had mentioned. Just wanted to highlight that but I am not going to state the vidoes up here cos I think it would make much more sense to go through them one by one in order to understand the whole big picture and actually learn something new other than whats mentioned above. Most the time there is more than one way to tackle an issue and this could be clearly learnt from this module.