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
Without seeing your UI design, I'm assuming:
It is likely you could use a single cell design, and then show/hide the different UI elements as needed.
For example:
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:
Each has its benefits and drawbacks... using an attributed string in a single label would be nice:
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:
and without the yellow:
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 hoursArray
s - 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
}
}