I’m trying to have a custom cell whom’s variable assignment changes based on if and else-if statements after the variable is declared.

This is to have the custom cell, which shows business hours of a selected business, be able to pick the custom table view cell that fits with the amount of open and closed time range pairs that I would like to show for the week for that business (as some businesses have more than one in one day).

However, I keep getting an error message saying:

Cannot assign value of type 'DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell' to type ‘DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell'

at this line of code:

detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell", for: indexPath) as! DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell

which is the line of code within the conditional statement of the first else-if statement, which changes the variable assignment of the custom cell variable I’m using. This line of code has a comment above it with the error message it got in the code snippet further below in this post. The conditional statement in the if-statement before this else-if statement, sets the value as the same value that the custom cell variable starts out as, and doesn’t get this type of error message, or any type of error message in it’s conditional statement.

I also get this same error message in the other else-if statements’ conditional statements, where the custom cell variable value is changed. There are also comments above these lines of code saying the error message that was received for that particular line of code in the code snippet further below.

What I’ve tried:

-Investigated whether using “as?” or “as” instead of force casting (using “as!”) in the lines of code where I’m getting these same types of error messages, might work, but it seemed to me like it wouldn’t.

How can I fix this error message, and/or how can I go about doing what I’m trying to do if the way I’m currently trying to do it won’t work (with the “correct” code being used)?

Thanks!

The code is below.

Code:

ViewController.swift:

import UIKit

//*Some code for a view controller for a table view file.*

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    if indexPath.row == 0 {
        
        //*Code for this cell goes here.*
        
        return custumCell
    }

    //*More "indexPath.row" code goes here for other table view cells being used.*

    if indexPath.row == 10 {
        
        var detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell", for: indexPath) as! DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell
        
        //If number of open hour time rnage pairs for all days is one.
        if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "OneTimeRange" {
            
            detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell", for: indexPath) as! DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell
        }
        
        //Else If number of open hour time rnage pairs for at least one day is two.
        else if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "TwoTimeRanges" {
            
            //Below is the line of code where I get the error message: "Cannot assign value of type 'DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell' to type 'DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell'".
            detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell", for: indexPath) as! DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell
        }
        
        //Else If number of open hour time rnage pairs for at least one day is three.
        else if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "ThreeTimeRanges" {
            
            //Below is the line of code where I get the error message: "Cannot assign value of type 'DetailsHoursContentsForAtLeastOneDayWithThreeOpenCloseTimeRangePairsTableViewCell' to type 'DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell'".
            detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAtLeastOneDayWithThreeOpenCloseTimeRangePairsTableViewCell", for: indexPath) as! DetailsHoursContentsForAtLeastOneDayWithThreeOpenCloseTimeRangePairsTableViewCell
            
        }
        
        //Else If number of open hour time rnage pairs for at least one day is more than three.
        else if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "GreaterThanThreeTimeRanges" {
            
            //Below is the line of code where I get the error message: "Cannot assign value of type 'DetailsHoursContentsForAtLeastOneDayWithGreaterThanThreeOpenCloseTimeRangePairsTableViewCell' to type 'DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell'".
            detailsHoursContentsCustomCell = scrollableCitiesRestaurantDetailsTableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAtLeastOneDayWithGreaterThanThreeOpenCloseTimeRangePairsTableViewCell", for: indexPath) as! DetailsHoursContentsForAtLeastOneDayWithGreaterThanThreeOpenCloseTimeRangePairsTableViewCell
        }
        
        //Setting hours as labels.
        detailsHoursContentsCustomCell.sundayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.sundayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.mondayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.mondayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.tuesdayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.tuesdayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.wednesdayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.wednesdayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.thursdayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.thursdayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.fridayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.fridayOpenHoursWithoutDay
        detailsHoursContentsCustomCell.saturdayHoursOpenWithoutDayLabel.text = selectedVenueDetailViewInfo.hours.saturdayOpenHoursWithoutDay
        
        return detailsHoursContentsCustomCell
    }
}

//*Some Extensions.*

The part of the model used for the business hours data that is shown in the detail view, after a restaurant is selected:

DetailViewModel.swift:

import Foundation

//*Other code for getting data such as name of restaurant, street address of restaurant, etc. for the detail view not using Codable. Am using NSDictionary.*

struct OpenHoursForDaysOfWeek {
    var mondayOpenHoursWithoutDay: String
    var tuesdayOpenHoursWithoutDay: String
    var wednesdayOpenHoursWithoutDay: String
    var thursdayOpenHoursWithoutDay: String
    var fridayOpenHoursWithoutDay: String
    var saturdayOpenHoursWithoutDay: String
    var sundayOpenHoursWithoutDay: String
    var numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse: String
}

My Table View Cell Layouts:

AllDaysWithOneTimeRangeTableViewCell

AtLeastOneDayWithTwoTimeRangesTableViewCell

AtLeastOneDayWithThreeTimeRangesTableViewCell

1

There are 1 best solutions below

15
DonMag On BEST ANSWER

Without seeing your UI design, I'm assuming:

  • the various cell types have some "common" UI elements
  • the differences are related to hours of operation?

It is likely you could use a single cell design, and then show/hide the different UI elements as needed.

For example:

enter image description here

That image shows 3 different prototype cells.

However, the bottom cell has 3 "rows" in a vertical UIStackView. We could easily turn that into the first cell by hiding the 2nd and 3rd "row" and setting the yellow and cyan label text.


If you feel your layout / design is too complex, and you want to stick with multiple cell prototypes, then I'd suggest having the cell classes handle parsing the data...

So each cell could have a function like this:

    public func fillTheData(_ venue: VenueStruct) {
        sundayHoursOpenWithoutDayLabel.text = venue.hours.sundayOpenHoursWithoutDay
        mondayHoursOpenWithoutDayLabel.text = venue.hours.mondayOpenHoursWithoutDay
        tuesdayHoursOpenWithoutDayLabel.text = venue.hours.tuesdayOpenHoursWithoutDay
        wednesdayHoursOpenWithoutDayLabel.text = venue.hours.wednesdayOpenHoursWithoutDay
        thursdayHoursOpenWithoutDayLabel.text = venue.hours.thursdayOpenHoursWithoutDay
        fridayHoursOpenWithoutDayLabel.text = venue.hours.fridayOpenHoursWithoutDay
        saturdayHoursOpenWithoutDayLabel.text = venue.hours.saturdayOpenHoursWithoutDay
    }

Presumably, each cell type would have different labels.text to set.

Then your cellForRowAt looks like this:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    if indexPath.row == 0 {
        //*Code for this cell goes here.*
        return custumCell
    }
    
    //*More "indexPath.row" code goes here for other table view cells being used.*
    
    if indexPath.row == 10 {
        
        //If number of open hour time rnage pairs for all days is one.
        if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "OneTimeRange" {
            let cell = tableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell", for: indexPath) as! DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell
            cell.fillData(selectedVenueDetailViewInfo)
            return cell
        }
        
        //Else If number of open hour time rnage pairs for at least one day is two.
        else if selectedVenueDetailViewInfo.hours.numberOfOpenHoursTimeRangesAsStringTypeOfTableViewCellToUse == "TwoTimeRanges" {
            
            //Below is the line of code where I get the error message: "Cannot assign value of type 'DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell' to type 'DetailsHoursContentsForAllDaysWithOneOpenCloseTimeRangePairTableViewCell'".
            let cell = tableView.dequeueReusableCell(withIdentifier: "DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell", for: indexPath) as! DetailsHoursContentsForAtLeastOneDayWithTwoOpenCloseTimeRangePairsTableViewCell
            cell.fillData(selectedVenueDetailViewInfo)
            return cell
        }
        
        // and so on
    }
}

Edit - after comments and seeing the layout goal...

Now that I've seen your layout, this is a simpler task. It can be done with a single cell class, and no need to show/hide elements at all.

Two options:

  • format each set of hours as an Attributed String
  • use stack views

Each has its benefits and drawbacks... using an attributed string in a single label would be nice:

enter image description here

except --- there are differences in attributed strings between iOS versions. Somebody who is more familar with it might know how to work around that.

Using stack views is pretty straightforward.

Here's how it would look with yellow backgrounds so we can see the label framing:

enter image description here

and without the yellow:

enter image description here

I don't know how you are getting your data, so I made a Venue struct that looks like this:

struct VenueStruct {
    // assuming there are some other properties
    var venueName: String = "Some Venue"
    var venueAddress: String = "123 Main Street"
    // etc...
    
    // array of operating hours
    //  this will be filled with 7 [String] arrays (one for each day of the week)
    //  each of those arrays will have 1 or more time ranges
    var hoursArray: [[String]] = []
}

Example hoursArrays - when filled with data:

["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
["11:00 am - 10:00 pm"]
-----
["7:00 am - 11:00 am", "12:00 pm - 10:00 pm"]
["10:00 am - 10:00 pm"]
["7:00 am - 11:00 am", "12:00 pm - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
-----
["7:00 am - 11:00 am", "12:00 pm - 4:00 pm", "5:00 pm - 10:00 pm"]
["10:00 am - 10:00 pm"]
["7:00 am - 11:00 am", "12:00 pm - 4:00 pm", "5:00 pm - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
["10:00 am - 10:00 pm"]
-----

So, here's a sample cell class - all via code, so no Storyboard or @IBOutlet connections:

class StackHoursCell: UITableViewCell {
    static let identifier: String = "StackHoursCell"
    
    let daysFont: UIFont = .systemFont(ofSize: 15.0, weight: .regular)
    let hoursFont: UIFont = .systemFont(ofSize: 15.0, weight: .regular)
    
    let outerStackView: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        v.spacing = 8
        return v
    }()
    
    var dayNames: [String] = [
        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
    ]
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        // we want to get the longest "day string" so we can align the time "column"
        //  we could use "Wednesday" but let's assume we may localize the app and
        //  "Wednesday" may not be the longest string
        
        // we could try German for example where "Donnerstag" ("Thursday") will be the longest
        //var calendar = Calendar.current
        //calendar.locale = Locale(identifier: "de_DE")
        //dayNames = calendar.weekdaySymbols
        
        var maxDayWidth: CGFloat = 0
        let v = UILabel()
        v.font = hoursFont
        dayNames.forEach { sDay in
            v.text = sDay
            v.sizeToFit()
            maxDayWidth = max(maxDayWidth, v.frame.width)
        }
        
        // fill the vertical stack view with 7 "rows"
        //  - horizontal stack views, each with Day name and Hours labels as arranged subviews
        for _ in 1...7 {
            let rowStack = UIStackView()
            rowStack.alignment = .top
            // "padding" so we have space to the time column
            rowStack.spacing = 32.0
            // day name label
            let v1 = UILabel()
            v1.font = daysFont
            v1.widthAnchor.constraint(equalToConstant: maxDayWidth).isActive = true
            // hours label
            let v2 = UILabel()
            v2.font = hoursFont
            v2.numberOfLines = 0
            v2.setContentHuggingPriority(.required, for: .vertical)
            v2.setContentCompressionResistancePriority(.required, for: .vertical)
            rowStack.addArrangedSubview(v1)
            rowStack.addArrangedSubview(v2)
            outerStackView.addArrangedSubview(rowStack)
            
            // during development, so we can see the label frames
            //[v1, v2].forEach { v in
            //  v.backgroundColor = .yellow
            //}
        }
        
        outerStackView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(outerStackView)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            // constrain outerStackView to the layoutMarginsGuid, with some
            //  left / top / bottom padding -- adjust to suit
            outerStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 24.0),
            outerStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
            outerStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            outerStackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
        ])
    }
    
    public func fillTheData(_ venue: VenueStruct) {
        var str: String = ""
        var row: Int = 0
        for (d, a) in zip(dayNames, venue.hoursArray) {
            // always safely unwrap optionals
            if let rowStack = outerStackView.arrangedSubviews[row] as? UIStackView {
                if let v1 = rowStack.arrangedSubviews[0] as? UILabel,
                   let v2 = rowStack.arrangedSubviews[1] as? UILabel {
                    v1.text = d
                    str = ""
                    a.forEach { hStr in
                        str += hStr
                        if hStr != a.last {
                            str += "\n"
                        }
                    }
                    v2.text = str
                }
            }
            row += 1
        }
    }
    
}

a simple sample data class to generate some sample values:

class SampleVenueData: NSObject {

    // various hours strings... just to make it easier
    //  to generate some sample data
    func hoursString(_ s: String) -> String {
        var str = ""
        switch s {
        case "11to10":
            str = "11:00 am - 10:00 pm"
        case "7to11":
            str = "7:00 am - 11:00 am"
        case "12to10":
            str = "12:00 pm - 10:00 pm"
        case "10to10":
            str = "10:00 am - 10:00 pm"
        case "12to4":
            str = "12:00 pm - 4:00 pm"
        case "5to10":
            str = "5:00 pm - 10:00 pm"
        default:
            str = "24 Hours"
        }
        return str
    }

    func sampleData() -> [VenueStruct] {
        
        var venueData: [VenueStruct] = []
        
        // some sample data
        var v: VenueStruct!
        
        v = VenueStruct()
        v.hoursArray = []
        for _ in 0..<7 {
            v.hoursArray.append([hoursString("11to10")])
        }
        venueData.append(v)
        
        v = VenueStruct()
        v.hoursArray = []
        
        v.hoursArray.append([hoursString("7to11"), hoursString("12to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("12to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        
        venueData.append(v)
        
        v = VenueStruct()
        v.hoursArray = []
        
        v.hoursArray.append([hoursString("7to11"), hoursString("12to4"), hoursString("5to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("12to4"), hoursString("5to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        
        venueData.append(v)

        v = VenueStruct()
        v.hoursArray = []
        
        v.hoursArray.append([hoursString("7to11"), hoursString("5to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("5to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("12to4"), hoursString("5to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("5to10")])
        v.hoursArray.append([hoursString("7to11"), hoursString("12to4"), hoursString("5to10")])
        v.hoursArray.append([hoursString("10to10")])
        v.hoursArray.append([hoursString("10to10")])
        
        venueData.append(v)
        
        // replicate it a few times to confirm scrolling works
        venueData.append(contentsOf: venueData)
        venueData.append(contentsOf: venueData)

        return venueData
    }
    
}

and a sample view controller:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var myData: [VenueStruct] = []
    
    let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
        
        tableView.register(StackHoursCell.self, forCellReuseIdentifier: StackHoursCell.identifier)
        
        tableView.dataSource = self
        tableView.delegate = self
        
        // let's get some sample data
        myData = SampleVenueData().sampleData()
        
        myData.forEach { v in
            v.hoursArray.forEach { h in
                print(h)
            }
            print("-----")
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: StackHoursCell.identifier, for: indexPath) as! StackHoursCell
        c.fillTheData(myData[indexPath.row])
        return c
    }
    
}