DiffableDataSource with multiple cell types - uitableview

I'm looking at DiffableDataSource available in iOS13 (or backported here: https://github.com/ra1028/DiffableDataSources) and cannot figure out how one would support multiple cell types in your collection or tableview.
Apple's sample code1 has:
var dataSource: UICollectionViewDiffableDataSource<Section, OutlineItem>! = nil
which seems to force a data source to be a single cell type. If I create a separate data source for another cell type - then there is no guarantee that both data sources don't have apply called on them at the same time - which would lead to the dreaded NSInternalInconsistencyException - which is familiar to anyone who has attempted to animate cell insertion/deletion manually with performBatchUpdates.
Am I missing something obvious?

I wrapped my different data in an enum with associated values. In my case, my data source was of type UICollectionViewDiffableDataSource<Section, Item>, where Item was
enum Item: Hashable {
case firstSection(DataModel1)
case secondSection(DataModel2)
}
then in your closure passed into the data source's initialization, you get an Item, and you can test and unwrap the data, as needed.
(I'd add that you should ensure that your backing associated values are Hashable, or else you'll need to implement that. That is what the diff'ing algorithm uses to identify each cell, and resolve movements, etc)

You definitely need to have a single data source.
The key is to use a more generic type. Swift's AnyHashable works well here. And you just need to cast the instance of AnyHashable to a more specific class.
lazy var dataSource = CollectionViewDiffableDataSource<Section, AnyHashable> (collectionView: collectionView) { collectionView, indexPath, item in
if let article = item as? Article, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Section.articles.cellIdentifier, for: indexPath) as? ArticleCell {
cell.article = article
return cell
}
if let image = item as? ArticleImage, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Section.trends.cellIdentifier, for: indexPath) as? ImageCell {
cell.image = image
return cell
}
fatalError()
}
And the Section enum looks like this:
enum Section: Int, CaseIterable {
case articles
case articleImages
var cellIdentifier: String {
switch self {
case .articles:
return "articleCell"
case .articleImages:
return "imagesCell"
}
}
}

One way of achieving this could be taking advantage your Section enum to identify the section with indexPath.section. It will be something like this:
lazy var dataSource = UICollectionViewDiffableDataSource<Section, Item> (collectionView: collectionView) { collectionView, indexPath, item in
let section = Section(rawValue: indexPath.section)
switch section {
case .firstSection:
let cell = ... Your dequeue code here for first section ...
return cell
case .secondSection:
let cell = ... Your dequeue code here for second section ...
return cell
default:
fatalError() // Here is handling the unmapped case that should not happen
}
}

Related

How to handle different data models with collectionView compositional layout?

Im updating an app which had many collectionViews embedded inside tableview so I decided to use UICollectionViewCompositionalLayout instead. I find them very flexible and I was able to design Home page for my app using this layout.
I followed some tutorials which just showed same data types, like Integers in multiple sections or photos app showing photos in different sections. In those tutorials they will use dataSource like this:
var dataSource: UICollectionViewDiffableDataSource<Section, PhotosItem>! = nil
or
var dataSource: UICollectionViewDiffableDataSource<Section, MoviesEntity>!
As my homepage consisted of many types of data, I simply used:
var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil
And simply used numberOfSections, numberOfItemsInSection and cellForItemAt methods. I used this approach as my homepage will get data from multiple api's and some sections are static.
In UICollectionView, I would simply hit an api and update my data model and reload collectionView and do same for different collectionViews handling different data models in same page but now there is only one CollectionView so how do I handle this?
Do I just hit 7-8 API's and reload collectionView everytime? What I would like to do is use snapshot feature of feeding CollectionView with data coming from multiple API's.
This is what I have done to create compositional layout:
func generateLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let isWideView = layoutEnvironment.traitCollection.horizontalSizeClass == .regular
let sectionLayoutKind = Section.allCases[sectionIndex]
switch (sectionLayoutKind) {
case .firstSection:
return self.generateFirstLayout(isWide: isWideView)
case .secondSection:
return self.generateCategoriesLayout(isWide: isWideView)
case .services:
return self.generateServicesLayout(isWide: isWideView)
case .spotlight:
return self.generateSpotlightLayout(isWide: isWideView)
case .offers:
return self.generateOffersLayout(isWide: isWideView)
case .reviews:
return self.generateReviewLayout(isWide: isWideView)
case .logo:
return self.generateLogoLayout(isWide: isWideView)
}
}
}
And im simply using these functions to generate sections and adding Dummy items in them:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return Section.allCases.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0 :
return 1
case 1 :
return categories.count
case 2 :
return services.count
case 3:
return 2
case 4:
return 5
case 5:
return offers.count
case 6:
return 4
default:
return 10
}
}
So far this is all dummy data and now I want to feed live data and this is not same as what those tutorials did but my sections are different and I could not create something like "PhotosItem" to feed into dataSource method like in those tutorials.
Those tutorials used something like this:
func configureDataSource() {
dataSource = UICollectionViewDiffableDataSource
<Section, AlbumItem>(collectionView: albumsCollectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, albumItem: AlbumItem) -> UICollectionViewCell? in
let sectionType = Section.allCases[indexPath.section]
switch sectionType {
case .featuredAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: FeaturedAlbumItemCell.reuseIdentifer,
for: indexPath) as? FeaturedAlbumItemCell else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
cell.totalNumberOfImages = albumItem.imageItems.count
return cell
case .sharedAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: SharedAlbumItemCell.reuseIdentifer,
for: indexPath) as? SharedAlbumItemCell else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
return cell
case .myAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: AlbumItemCell.reuseIdentifer,
for: indexPath) as? AlbumItemCell else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
return cell
}
}
dataSource.supplementaryViewProvider = { (
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath) -> UICollectionReusableView? in
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: HeaderView.reuseIdentifier,
for: indexPath) as? HeaderView else { fatalError("Cannot create header view") }
supplementaryView.label.text = Section.allCases[indexPath.section].rawValue
return supplementaryView
}
let snapshot = snapshotForCurrentState()
dataSource.apply(snapshot, animatingDifferences: false)
}
But i don't have one AlbumItem but like ReviewItem, OfferItem and many more. So I was wondering do I stick to my way and just call the api and reload my collectionView?
I have seen many apps that probably have same situation as me. So can anyone help me how to deal with this?
Im using SwiftyJSON to feed my data into my data models. Just telling this as I have seen in most examples they use Hashable.
I found answer by making my Model Hashable and had to switch from SwiftyJSON to Codable completely which I was avoiding for months. When your model is Hashable, your can pass it as item for your section. You can create different data models and add them to any section as required.
So for above, if I want to add PhotoItems or MovieItems or anything else, first I need to create Sections which can be done by creating an Enum like this:
enum Section {
case Section1
case SEction2
}
And then create another enum for items for each section like this:
enum Items {
case Section1Item(PhotoItem)
case Section2Item(MovieItem)
}
then define your dataSource like this:
var dataSource: UICollectionViewDiffableDataSource<Section, Items>!
As you can see now we can add items of any type as we please instead of defining Item type like in question.
Finally just create a function to define snapshot:
var snapshot = Snapshot()
snapshot.appendItems(ArrayOfItemType1.map(PhotosItem.photos), toSection: Section.first)
snapshot.appendItems(ArrayOfItemType2.map(MovieItem.movie), toSection: Section.first)
datasource.apply(snapshot, animatingDifferences: true)

Group similar UITableViewCell using protocol to reduce code

Some Background
Let's say I have to display a form which can have various types of components:
Text Field
Text Area
Image
Video
Dropdown
I have created UITableViewCell for each one but since they all are form components and each and every one of them have some properties common in them like some data(formData, userEntered data) and more I made a protocol and conformed all these cells to this protocol
protocol FormComponent where Self: UITableViewCell {
var property1: String? { get set }
var property2: String? { get set }
}
And conformed my cells like this
class TextFieldCell: UITableViewCell, FormComponent {
var property1: String?
var property2: String?
}
Problem
Now when I have to decide which UITableViewCell I have to create, I have to make a switch statement and decide which form component is to be created
// Some field that I get from some computation and this same will go inside every cell no matter the type
let field = computeFieldValue()
switch fieldType {
case textField:
let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_FIELD_CELL, for: indexPath) as! TextFieldCell
cell.property1 = field.property1
cell.property2 = field.property2
return cell
case textArea:
let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_AREA_CELL, for: indexPath) as! TextAreaCell
cell.property1 = field.property1
cell.property2 = field.property2
return cell
}
Now rather than initialising the cell and assigning the property inside the case of switch, I wanted to use protocol advantage to initialise the cell outside the switch as a sort-of protocol type so that I can assign the values of properties outside the switch and don't need to assign the same values to properties inside each case
// Some field that I get from some computation and this same will go inside every cell no matter the type
let field = computeFieldValue()
var cell = // cell initialisation which will be of type FormComponent so that I can access the property values directly from here
cell.property1 = field.property1
cell.property2 = field.property2
switch fieldType {
case textField:
// Converting that cell to TextFieldCell with the properties defined above intact
case textArea:
// Converting that cell to TextAreaCell with the properties defined above intact
}
return cell
I think it's sort of like Upcasting, I am not so sure how to achieve this. And if there is some properties specific to a single fieldType, I can maybe downcast to like cell as! TextFieldCell inside the case part and assign that
And I have a lot of properties and a lot of cases(fieldType) to handle so this approach will reduce the code a lot
Few Model and Classes creations. You already had implemented these but for the check I added them
FieldType enum
enum FieldType {
case textArea, textLabel, textField
}
Field struct Model. You may be using class and might be you have more properties
struct Field {
var type : FieldType
var property1 : String
var property2 : String
}
Now your protocol as you already mentioned in your question
protocol FormComponent where Self: UITableViewCell {
var property1: String? { get set }
var property2: String? { get set }
}
And some different kind cells, one of them is mentioned in the question.
class TextFieldCell: UITableViewCell, FormComponent {
var property1: String?
var property2: String?
}
class TextAreaCell: UITableViewCell, FormComponent {
var property1: String?
var property2: String?
}
class TextLabelCell: UITableViewCell, FormComponent {
var property1: String?
var property2: String?
}
Now to make all these things in work you need to use protocol extension as I mentioned in comments.
extension FormComponent {
func setProperties(p1 : String, p2 : String) {
self.property1 = p1
self.property2 = p2
}
}
Now in cellForRow, to make reusable code, you need to code like below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let field = arrFields[indexPath.row]
var cell : FormComponent?
switch field.type {
case .textArea:
cell = tableView.dequeueReusableCell(withIdentifier: "TextAreaCell") as! TextAreaCell
case .textLabel:
cell = tableView.dequeueReusableCell(withIdentifier: "TextLabelCell") as! TextLabelCell
case .textField:
cell = tableView.dequeueReusableCell(withIdentifier: "TextViewCell") as! TextFieldCell
}
cell!.setProperties(p1: field.property1, p2: field.property2)
return cell!
}
Edit: Probably it should work as you are type casting FormComponent into TextFieldCell. If in case it is not working then try below code inside switch case
case .textField:
cell = tableView.dequeueReusableCell(withIdentifier: "TextFieldCell") as! TextFieldCell
let newObject = cell as! TextFieldCell // type cast your cell
newObject.yourProperty = "New Property" // access your specified property
That's it. 😋
Note : In cellForRow, cell object is optional, so make sure it won't by passed your switch-cases else your app will be crashed.
Whenever I try to take advantage of Swift features to simplify the development and make it more fun, UIKit always makes sure to make it super hard or even impossible so its easy to get discouraged and frustrated.
What you are trying to achieve is something which every app may have its own solution for, since there are so many ways to approach this problem and many will go with the solution appropriate for their needs (which may differ significantly from project to project).
In my case, in the project I am currently working at I have applied this strategy, for implementing table views:
Define an enumeration of items that will be created to build the list, in your case it could be:
enum FormListItem {
case textField(String, String) // eg. text value, placeholder
case textArea(String) // text value
case image(UIImage)
case video(URL)
case dropdown([String])
}
Build model structure for the table view, and easily supply data for table view data source protocol methods (number of sections and rows)
private var items = [[FormListItem]]() { didSet { tableView.reloadData() } }
private func buildItems() {
items = [[.textField(“text”, “sample")]]
}
items.count // number of sections
items[section].count // number of rows in section
In cellForRow method access item for indexPath, switch over the cases and take advantage of Swifts associated values for enumeration cases to pass appropriate data in a elegant manner.
let item = items[indexPath.section][indexPath.row]
switch item {
case .textField(let property1, let property2):
let cell = tableView.dequeueReusableCell(
withIdentifier: TEXT_FIELD_CELL, for: indexPath
) as! TextFieldCell
cell.property1 = property1
cell.property2 = property2
return cell
// … handle other cases
}
This architecture serves me well, and integrates nicely with MVC and MVVM. I have been using this approach in quite a while and it is implemented across the whole project I am currently working at that is in production. It provides a very readable interface for what the table view is supposed to be displaying and the cells it is built with. But most importantly it enables me to pass exactly the data I want to cells and not having them exposed to different types of input not related to them. I realize that you still need to manually dequeue every cell separately but sometimes it is not worth fighting UIKit, cause you can waste a lot of time and achieve little, which makes things even worst. Not going too deep with abstractions will also make it easy to adjust the codebase to any changes in the future.

Swift 4 - Multiple tableview cell types in a random order

I am creating a to do list app and in the list tableview, there needs to be 3 types of tableview cell. One for a task, one for an event and one for a reminder. Each cell has different elements inside it (multiple labels, different images etc...)
I would like to be able to use 3 different cell types in the same tableview.
I have created 3 UITableViewCell classes, each with the cell constraints and setup inside. And within my core data model, there are 3 entities. One for a task, event and reminder.
Also, it's a to do list app so the cells should be arranged in any order and any number of each custom cell.
So using:
if indexPath.row == 0 {...} etc...etc...
would not be viable.
Any help or ideas on how to achieve this would be greatly appreciated.
You need the dequeue the cell according to the kind of item you have, not according to the indexPath.
Either you have three Struct/Classes for your Event/Reminder/Task:
struct Event { ... }
struct Reminder { ... }
struct Task { ... }
And:
var myTableViewArray: [Any] //Array of either Event, Reminder, Task objects.
In tableView(_:cellForRowATt:):
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = myTableViewArray[indexPath.row]
if let event = object as? Event {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventCellId" for:indexPath) as EventCell
//configure cell with event
return cell
} else if let task = object as? Task {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCellId" for:indexPath) as TaskCell
//configure cell with task
return cell
} else if let reminder = object as? Reminder {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReminderCellId" for:indexPath) as ReminderCell
//configure cell with reminder
return cell
} else {
//We shouldn't get into this case, but return an UITableViewCell here?
}
}
If you have the same object, or because you added a protocol simulating that, but with a property giving if it's an Event/Reminder/Task, like an enum:
enum Type {
case event
case reminder
case task
}
struct CustomObject {
let type: Type
}
Then, instead of:
if let event = object as? Event {}
else if let task = object as? Task {}
else if let reminder = object as? Reminder {}
Just do a simple switch:
switch object.type {
case .event:
//Dequeue the EventCell
case .task:
//Dequeue the TaskCell
case .reminder:
//Dequeue the ReminderCell
}
Add an attribute to your data model(s) that will keep track of its sorting order in the current todo list. Sort that data model with respect to that attribute and it'll be ready for the table indexPath.
It could be an Integer. When switching todo locations, you modify this attribute.
This opens up a potential overhead because you have to update other todos as well and their sorting order. (eg: todo #5 moved to location #2, have to modify the attribute of the bottom part todos of the list).
If you aren't expected to manage tens of thousands of todos that could be sufficient. Oherwise, you'd need more sophisticated mechanism such as floats or strings and normalize them once in a while in the background.

Swift: Cast tableCell as variable class

I'm trying to refine some working but ugly code.
My app has five TableViews, each one displaying a different type of data (with different cell layouts). Because the datatypes are similar-ish and require many similar methods (for downloading, encoding, etc), I have set up a TableViewController:UITableViewController class to serve as a superclass for the five TableViewController subclasses. Within this superclass, I have the standard "cellForRowAt" method, but it's bloated and repetitive. I want to simplify it.
My problem (I think) is the multiple "let cell = " statements, which all cast as a different type of TableViewCell depending on the datatypes. For example, my DataType.SCHEDULES datatype needs to get a SchedulesTableViewCell with reuseID of "SchedulesCell". I can't make them all the same TableViewCell class, because they each have their own IBOutlet views.
Making things uglier, each tableView has two cell prototypes, and I need to be able to generate an ARTICLE cell and a DETAIL cell for each datatype.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// get the article and row type
let article = getArticleFor(indexPath: indexPath)
let cellType = getCellTypeFor(indexPath: indexPath)
// create either an ARTICLE row or a DETAIL row.
// (simplified for SO posting. Each "case" is actually
// 5-6 lines of nearly identical code)
switch cellType {
// for the ARTICLE cell prototype
case CellType.ARTICLE:
// get the right table cell matching the datatype
switch self.datatype {
case DataType.SCHEDULES:
let cell = tableView.dequeueReusableCell(withIdentifier: "SchedulesCell") as! SchedulesTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.LUNCH:
let cell = tableView.dequeueReusableCell(withIdentifier: "LunchCell") as! LunchTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.EVENTS:
let cell = tableView.dequeueReusableCell(withIdentifier: "EventsCell") as! EventsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.DAILY_ANN:
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyannCell") as! DailyannTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.NEWS:
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell") as! NewsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
}
// or for the DETAIL cell prototype
case CellType.DETAIL:
// get the right table cell matching the datatype
switch self.datatype {
case DataType.SCHEDULES:
let cell = tableView.dequeueReusableCell(withIdentifier: "SchedulesDetailsCell") as! ScheduleDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.LUNCH:
let cell = tableView.dequeueReusableCell(withIdentifier: "LunchDetailsCell") as! LunchDetailsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.EVENTS:
let cell = tableView.dequeueReusableCell(withIdentifier: "EventsDetailsCell") as! EventsDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.DAILY_ANN:
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyannDetailCell") as! DailyannDetailsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.NEWS:
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsDetailCell") as! NewsDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
}
}
}
I originally had each "let cell =" case within the subclasses' own "cellForRowAt" methods, but I was repeating very similar code in every subclass, which seemed silly. On the other hand, the code above moved the repetition into a single class, but didn't remove the repetition, so it's still silly, but in a different place.
I feel like if I could make a dictionary of classes, something like this...
let tableCellClasses = [DataType.SCHEDULES : ScheduleTableViewCell,
DataType.LUNCH : LunchTableViewCell
etc.
...then I could make my "let cell = " statements more generic, like...
let cell = tableView.dequeueReusableCell(withIdentifier: identifier[dataType]) as! tableCellClasses[dataType]
but can't seem to find a way to make it work.
As I said, it works but it's ugly. I work in a high school, so I'd like for students viewing the repo to see clean, well-structured code -- so I'm shooting for better than just "it works."
Any suggestions?
You could use Swift's meta types and what not, but looking at your code, all your cell subclasses share the same methods:
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
Why not:
Have a base class from which all custom cell classes inherit, that implements the above interface (the three methods you use after dequeuing a cell, with the possibility of them being overriden on each concrete subclass), so dequeue once and force-cast into the base type (I believe the right implementation of the methods will be executed, for each subclass. The cast is only to make the compiler happy: UITableViewCell does not have those methods).
Have a switch clause on the data type that gives you the specific cell identifier
Have each prototype cell set to the specific class on the storyobard, and assign the specific identifier too.
Does it make sense?
Now, form looking at your code, it doesn't look like you really need different subclasses. It's perfectly okay to have several different protoypes of the same UITableViewCell subclass, each with a different subview layout and a different reuse identifier, as long as they all can work with the same number and type of subviews and other custom properties/methods.
There is no way to do what you assume. You have to casting the classes one by one for your need. Or you can use base class which implemented all methods you need and calling them by the datatype.

Switch statement for the tag property of a view

I'm trying to set up a tableview that has 2 cells. Both cells have their own classes and they have their own methods to configure them. In code, when i get to the cellforrowatindexpath method, i get stuck. I can only dequeue one of the cells and call it's methods, which means the other cell won't configured. I want to configure both. Here's what i'm (currently) trying in the cellforrow method:
let cells = [tableView.viewWithTag(1), tableView.viewWithTag(2)]
for view in cells {
var reuseIdentifier = "cellID"
switch view?.tag {
case 1: // error occurs here
reuseIdentifier = "storyCell"
let storyCell1 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell1
storyCell1.theCategory.text = newsItem.storyCategory
storyCell1.theTitile.text = newsItem.titleText
storyCell1.firstParagraph.text = newsItem.paragraph1
case 2: // error occurs here too
reuseIdentifier = "storyCell2"
let storyCell2 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell2
storyCell2.configureCell(newsItem)
default:
}
in the storyboard, i have given both those cells tags of 1 and 2 respectively, hence the array at the very beginning. I have 2 problems with this approach.
I can't use it because the switch statement is throwing me an error: Expression pattern of type 'Int' cannot match values of type 'Int?'
even if it were to not have the error above, i can still only return one cell at the end of the method.
any help on my approach or a different, better way to handle this would be appreciated. Thanks!
EDIT:
Since I'm sure i've added the tag property i force unwrapped the view!.tag property and the error goes away. So, the 2nd question now remains.
I don't really get what you want to do here.
What I think you're trying to do, is to configure and return two cells in the tableView(_:cellForRowAtIndexPath:) method. If you really want to do this, you're totally doing it wrong.
The table view's data source methods asks questions. And your job is to answer those questions by returning a value. For example, numberOfSectionsInTableView(_:) asks you how many sections should there be. An example answer might be return 1, return 10 etc.
Similarly, tableView(_:cellForRowAtIndexPath:) asks
What should be the cell that should be shown in the section and row specified by the index path?
And you answer by returning a UITableViewCell. You can't return two cells because it is asking you to provide a cell to be displayed at that specific section and row. If you gave it two cells to display, how can the table view display them? It doesn't make sense! Each row in the table view can only display one cell!
Therefore, instead of giving tags to the cells, use the indexPath parameter to decide which cell to create.
Let's say you want the first row to display the cell with the identifier "storyCell". And you want the second row to display the cell with the identifier "storyCell2". And your table view has only one section. You can just do this:
switch indexPath.row {
case 0:
reuseIdentifier = "storyCell"
let storyCell1 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell1
storyCell1.theCategory.text = newsItem.storyCategory
storyCell1.theTitile.text = newsItem.titleText
storyCell1.firstParagraph.text = newsItem.paragraph1
return storyCell1
case 1:
reuseIdentifier = "storyCell2"
let storyCell2 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell2
storyCell2.configureCell(newsItem)
return storyCell2
default:
// this shouldn't be reached if you do your other data source methods correctly
return UITabeViewCell()
}
And you should delete these nonsense:
let cells = [tableView.viewWithTag(1), tableView.viewWithTag(2)]
for view in cells {
var reuseIdentifier = "cellID"

Resources