listviewswiftuiswiftui-foreach

Getting error: (Type '()' cannot conform to 'View') even though I am returning a view


I'm not sure why I am getting this error: Type '()' cannot conform to 'View' After looking up the error, I understand that xcode is saying that my code is not returning a view. However, my code is returning a view. So I am having trouble understanding why I am getting the error...

Please see below code where the error occurs:

/// A list to display time logs
struct TimeLogsList: View {
    
    /// The view model required to generate this view with data
    @State var model = TimeLogsListViewModel(timeLogs: MockData.sharedMockData.timeLogs)
    
    var body: some View {
        // Generating a List view with timeLogs sectioned by date from the view model
        List() {
            // Looping through each date in sections
            model.sections.forEach { date in
                // Setting each Section Title as the date in the current loop
                Section(header: Text("\(date)")) {
                    // Looping through each timeLog in timeLogs
                    $model.timeLogs.forEach { $timeLog in
                        // If the timeLog holds the same date as the date in the loop then...
                        if $timeLog.date == date {
                            // ...timeLog is added as a new row
                            TitleWithHoursListRowView(timeLog: $timeLog)
                        }
                    } // End of TimeLogs Loop
                }
            } // End of Sections Loop
        }
    }
}

Here is the code you will need to reproduce the error (I have included inline comments to make this easy to understand.)

A struct: TimeLogsListViewModel:

/// A model to be passed into a List View of timeLogs sectioned by date
struct TimeLogsListViewModel {
    
    /// An array of timeLog dates to be used as sections for a List View - can only be set via `self.generateSections()`
    private(set) var sections = [Date]()
    
    /// The timelogs for the List View
    var timeLogs: [TimeLog]
    
    /// Initializes a TimeLogsListViewModel
    /// - parameter timeLogs: An array of TimLog
    init(timeLogs: [TimeLog]) {
        self.timeLogs = timeLogs
        generateSections()
    }
    
    /// Gets the dates from an array of timeLogs and forms an array of those dates - no dates are duplicated.
    mutating func generateSections() {
        var sections: [Date] = []
        // Looping through each timelog, adding its date to the sections array if not already
        self.timeLogs.forEach { timeLog in
            if !sections.contains(timeLog.date) {
                sections.append(timeLog.date)
            }
        }
        // Sorting the dates in the sections array
        sections = sections.sorted(by: { $0.compare($1) == .orderedDescending })
        // Updating self.sections to contain an array of sorted dates
        self.sections = sections
    }
}

A struct: TimeLog:

/// A data struct that holds the data of a single time log
struct TimeLog: Identifiable {
    // TimeLog UUID to conform to identifiable
    var id = UUID()
    /// The date for when the logged data was completed
    var date: Date
    /// The data the time log contains
    var details: Details
    
    /// A data struct modeling the data a time log contains
    struct Details {
        /// A brief description describing the work that was done
        var shortDescription: String
        /// A more detailed description describing the work that was done
        var longDescription: String?
        /// The amount of hours that was spent to complete the work for this log
        var hours: Double
    }
}

A class: MockData:

/// This singleton holds mock data used for testing
class MockData {
    
    // MARK: Static Properties
    
    /// The shared instance for MockData
    static let sharedMockData = MockData()
    
    // MARK: Instance Properties
    
    // Dates
    var dateOne: Date
    var dateTwo: Date
    var dateThree: Date
    var dateFour: Date
    
    // TimeLogs
    var timeLogOne: TimeLog
    var timeLogTwo: TimeLog
    var timeLogThree: TimeLog
    var timeLogFour: TimeLog
    
    // TimeLog arrays
    var timeLogs: [TimeLog]
    
    // MARK: Init
    
    /// A private init making MockData a singleton
    private init() {
        let formatter = DateFormatter()
        formatter.dateFormat = "MM/dd/yy"
        // Initializing dates
        dateOne = formatter.date(from: "01/02/23") ?? Date()
        dateTwo = formatter.date(from: "01/04/23") ?? Date()
        dateThree = formatter.date(from: "02/05/23") ?? Date()
        dateFour = formatter.date(from: "04/11/23") ?? Date()
        // Initializing timeLogs
        timeLogOne = TimeLog(date: dateOne, details: TimeLog.Details(shortDescription: "First Time Log", hours: 10))
        timeLogTwo = TimeLog(date: dateTwo, details: TimeLog.Details(shortDescription: "Second Time Log", hours: 8))
        timeLogThree = TimeLog(date: dateThree, details: TimeLog.Details(shortDescription: "Third Time Log", hours: 11))
        timeLogFour = TimeLog(date: dateFour, details: TimeLog.Details(shortDescription: "Fourth Time Log", hours: 11))
        timeLogs = [timeLogOne, timeLogOne, timeLogTwo, timeLogThree, timeLogFour]
    }
}

And a View: TitleWithHoursListRowView:

/// A view used for displaying a title and a log of hours - intended to be used in a list row.
struct TitleWithHoursListRowView: View {
    
    @Binding var timeLog: TimeLog
    
    var body: some View {
        HStack() {
            Text(timeLog.details.shortDescription)
            Spacer()
            Text("Hours:")
            Text("\(Int(timeLog.details.hours))")
        }
    }
}

Thanks ahead everyone :)


Solution

  • Could you try this:

    ForEach(model.sections, id:\.self) { date in
        Section(header: Text("\(date)")) {
            ForEach($model.timeLogs) { $timeLog in
                if timeLog.date == date {
                    TitleWithHoursListRowView(timeLog: $timeLog)
                }
            }
        }
    }
    

    You can change the timeLog or date Bindings as per your need.