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.
Related
I have a function that receives an argument of a specific class type, but I want to make it dynamic so that I could pass the argument from another class name as well.
func unselectedTopCell(cell: SearchExploreCollectionViewCell) {
let color = UIColor.init(named: "textBlack") ?? .black
let borderColor = color.withAlphaComponent(0.12)
let textColor = color.withAlphaComponent(0.87)
cell.cellBGView?.borderColor = borderColor
cell.cellBGView?.borderWidth1 = 1
cell.titleLabel?.textColor = textColor
}
Now I want to reuse this function for another collection view cell, but how would I pass other class name instead of "SearchExploreCollectionViewCell" at calling time?
Conform the classes you are going to pass as arguments under a common protocol. By doing so, you can give the argument's datatype as the protocol and any class that conforms to that protocol can be used in its place.
If I understand correctly, you can fix this:
First, you can write an extension for it. It makes it a lot easier.
public extension UICollectionView {
func cellWithIdentifierAndIndexPath<T:UICollectionViewCell>(cell:T.Type,indexPath:IndexPath) -> T {
let genericCell = self.dequeueReusableCell(withIdentifier: T.className, for: indexPath) as! T
return genericCell
}
}
Then, inside your cellForItemAt function:
var cell = UICollectionViewCell()
switch yourCellType {
case searchExplore:
cell = collectionView.cellWithIdentifierAndIndexPath(cell: SearchExploreCollectionViewCell.self, indexPath: indexPath)
case someThingElse:
cell = collectionView.cellWithIdentifierAndIndexPath(cell: SomeThingElse.self, indexPath: indexPath)
default:
cell = collectionView.cellWithIdentifierAndIndexPath(cell: NoDataCell.self, indexPath: indexPath)
}
return cell
Important note: do not forget to set the cell's reusable id as the cell's class name.
For example, if you have a cell that have a name like "NoDataCell", you should use "NoDataCell" as its reuse identifier.
So I don't know if the title gets my problem right but here is the thing:
I have an enum for identifying several types of table view cells. Every type of cell has its own class. I have conformed every one of those to a protocol but with an associated type. I am now trying to create a way to have instances of those classes and use their protocol methods and properties arbitrarily. Here's an example:
enum CellType {
case a, b, c
func getCellClass() -> ProtocolWithAssociatedType.Type { //Error here
switch self {
case .a: return ClassA.self
case .b: return ClassB.self
case .c: return ClassC.self
}
}
This enum raises an error of Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements on that line.
So this is the exact protocol I have except the name:
protocol ProtocolWithAssociatedType: UITableViewCell {
associatedtype Delegate
var delegate: Delegate? {get set}
func setupCell()
}
All the classes ClassA, ClassB and ClassC conforms to this. They all have their own delegate protocols which they cast with typealias e.g.:
protocol ClassADelegate: class {
...
}
class ClassA: UITableViewCell, ProtocolWithAssociatedType {
typealias Delegate = ClassADelegate
weak var delegate: ClassADelegate?
func setupCell() {}
...
}
extension ViewController: ClassADelegate {
...
}
All of these is to slim down the tableView(...cellForItemAt:...) and other similar methods since there are many cell classes in this project and it's beyond the point of being readable and it's really really hard to make any development on this particular view controller because of this.
I have an extension for UITableView for creating reusable cells for those which it's reusable id is the same as it's class' name like this:
func dequeueReusableCell<C>(_ cellType: C.Type, for indexPath: IndexPath) -> C where C: UITableViewCell {
let identifier = String(describing: cellType)
guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? C else {
fatalError("Failed to find cell with identifier \"\(identifier)\" of type \"\(C.self)\"")
}
return cell
}
So I am willing to use this like following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = CellDataArray[indexPath.row].cellType
let cellClass = cellType.getCellClass()
let cell = tableView.dequeueReusableCell(cellClass, for: indexPath) \\ Here I have to have the cell conform to the protocol somehow
cell.delegate = self \\So here is the associated type which doesn't need to be read but is need to be written only
cell.setupCell() \\Obviously there are going to be some data passed to the cell instance right here
return cell
}
I have read so many questions and answers and I am trying every one of them to accomplish this yet I haven't been able to do so. I am trying to avoid massive functions in the view controller and make things as modifiable as possible. All those cells are acting like little view controllers themselves and they all have to communicate with the view controller. Also the cells are not static, like for different occasions there are different cells I have to show. Right now even adding one simple cell is a hell of a work, let alone creating a whole new occasion. But with this approach I am trying to make things... modular.
So is there any way I can do this without having a runtime crash here and there or creating a blackhole and bringing the universe to an end?
EDIT: I have tried the generic type way but unable to do so. Because of the way I want the func getCellClass() work, it is not possible to make the complier know what that generic type will be. Like following:
func getCellClass<C>() -> C.Type where C: ProtocolWithAssociatedValue {
...
}
So even I force cast the return values to the C.Type then I have problem where I call it simply because C is not known.
EDIT
I have removed the associated value from the protocol and did the following:
protocol ProtocolWithAssociatedType: UITableViewCell {
var _delegate: AnyObject? {get set}
func setupCell()
}
protocol ClassADelegate: class {
...
}
class ClassA: UITableViewCell, ProtocolWithAssociatedType {
weak var _delegate: AnyObject? { didSet { delegate = _delegate as? ClassADelegate } }
private weak var delegate: ClassADelegate?
func setupCell() {}
...
}
extension ViewController: ClassADelegate {
...
}
This way the protocol is usable as return type of that enum function and I can force cast the reusable cell to it since the protocol itself conforms to UITableViewCell. I have built without any problem yet can not test yet (test servers are down outside of working hours). When I test it and if it runs without any problem, I will post it as solution.
Seems you're trying to mix polymorphic behaviour (i.e. the array of cell types) with static behaviour (protocols with associated types), and this is more or less not possible with the current Swift design.
Protocols with associated types are determined at compile time, while the code in your tableView(_:cellForRowAt:) method is executed at runtime, and this makes it somewhat incompatible with protocols with associated types.
let cell = tableView.dequeueReusableCell(cellClass, for: indexPath)
In the above code you need to be able to tell the compiler which type do you expect for the cell, in order to inject the delegate, however that's a compile-time feature, and you need it at runtime, hence the incompatibility.
I'm afraid you'll have to stick to runtime error propagation/handling for this. Unless you are willing to change the delegate to match the whole set of possible delegates, and in this case the associated type is no longer needed, which enables the runtime features.
protocol MyCell: UITableViewCell {
var delegate: CellDelegate1 & CellDelegate2 ... & CellDelegateN { get set }
func setupCell(SomeCommonProtocolThatGetsCastedAtRuntime)
}
SOLUTION
So after a couple of days' work and trial-error I have finally came up with a solution. Thanks to #Cristik I have had an insight and changed my approach.
Firstly I have changed my way of handling the delegates. I had a different delegate for every cell. Now I have only one which all of those cells use.
So this way I was able to make my property like following:
protocol CellProtocol: UITableViewCell {
var delegate: CommonCellDelegate?
func setupCell(...)
}
For all the properties and some common methods of the view controller which the cells will use I have declared the delegate protocol like following:
protocol CommonCellDelegate: class {
var viewControllerProperty1: SomeClass1 { get set }
var viewControllerProperty2: SomeClass2 { get set }
...
var viewControllerPropertyN: SomeClassN { get set }
func someCommonFunction1(...) -> ...
func someCommonFunction2(...) -> ...
...
func someCommonFunctionN(...) -> ...
}
As my intention from the beginning being to keep the view controller nice and clean, I have put those uncommon methods which cells individually need down below those cells' class declarations as an extension to the delegate property:
class SomeCellClass: CellProtocol {
var delegate: CommonCellDelegate?
func setupCell(...) {
...
}
...
}
extension CommonCellDelegate {
func someMethodForTheCellAbove1(...) -> ... {
...
}
func someMethodForTheCellAbove2(...) -> ... {
...
}
.
.
.
}
As I didn't need the enum method to return different classes anymore, I have changed it a bit to return the reuse id of every cell type. So no need to return the class type there:
enum CellType {
case cellA, cellB, cellC, ...
func getCellClass() -> String { //Error here
switch self {
case .cellA: return String(describing: ClassA.self)
case .cellB: return String(describing: ClassB.self)
case .cellC: return String(describing: ClassC.self)
...
// Note: We use the the name of the cell classes as their reuse id's for simplicity.
}
}
And finally, I was able to create those cells in the tableView(_:cellForRowAt:) method quite easily and without creating a rip in the fabric of the space-time:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = CellDataArray[indexPath.row].cellType
let cellReuseId = String(describing: cellType.getCellId())
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) as? CellProtocol else { fatalError("some fatal error") }
cell.delegate = self
cell.setupCell(...)
return cell
}
This way it's very very easy to make new implementations since all that needs to be done is to create the class, make the class conform to the CellProtocol, make a case for it in the enum, add the case to the array whereever or whenever it's needed, register the cell id and voilĂ ! It's there! And because all the needed properties needed are now known by the cell thanks to the delegate, they can easily use those like their own properties, only difference is that it's not self.something but delegate?.something.
I hope this effort of mine might be helpful to others. Cheers!
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
}
}
I have a TableView in a view controller and a UILabel in the UIViewController too. Everything gets displayed well but I am trying to achieve something and I cannot simply get the Logic behind it.
The tableview cell has a UILabel that has Int values on the cells, thiese numbers varies now how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller.
I have tried creating a variable in the UIView controller and adding this value to t but I am unable to do that because I cannot add this number together
any help as to how to do this. Thanks
var total: Double?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! BrokageCheckoutCell
cell.configure()
total = Double("\(cell.subPrice.text!)")
cell.selectionStyle = .none
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId2, for: indexPath) as! OtherCheckoutCell
cell.configure()
cell.selectionStyle = .none
return cell
default: break
}
return UITableViewCell()
}
You ask:
how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller
In short, you donât. The label in the table view cell (which is part of the âviewâ) is not our source of data. Itâs only used to show the individual strings.
For example, letâs say your app has 100 items to be summed, but the appâs table view shows, say, only 12 rows at a time. As a memory and performance improvement, iOS will reuse cells that scroll out of view in order to show the contents for new rows that scroll into view. But this means that you canât say âsum all of the contents of the labels in those 100 cellsâ because there arenât 100 cells; there are only 12.
Your app should instead refer to its âmodelâ, that structure (e.g., an array) that cellForRowAt used to determine what to show in a given table view cell. So, if you want to add up a grand total, you should add up the values in that array, not referring to the cells at all.
And if your cell has controls that allow data to be modified, then the cell should initiate the update of the model. Then, the process of calculating the grand total (summing the values in the array) still works.
So, letâs consider an example:
You should have a model that contains the numbers that you want to sum. Your example is a little unclear, so Iâll create an example where each table row contained a name, a unit price, and a quantity:
struct Item {
let name: String
let unitPrice: Decimal
var quantity: Decimal
}
In this example, the total for any given row will be the quantity times the unit price. And the grand total for all the rows will be the sum of all of those products.
Then your view controller might have an array of those for its model:
var items: [Item] = ...
Then when you want to update your grand total label, youâd just have a method that calculated the quantity times the price for the total for each row and then sum up those values to calculate the grand total. It will then update the grand total text field accordingly:
private extension ViewController {
func updateTotal() {
let total = items
.map { $0.quantity * $0.unitPrice }
.reduce(0, +)
totalLabel.text = totalFormatter.string(for: total)
}
}
Note, I am not retrieving these numbers from the cells (because those might be reused), but rather Iâm retrieving all the data I need from the model.
The key here, though, is if your cell, for example, allowed the user to change one of those values, you need a mechanism to update the table view controllerâs model. Weâll use a protocol for that:
protocol ItemCellDelegate: class {
func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal)
}
class ItemCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var quantityTextField: UITextField!
#IBOutlet weak var unitPriceLabel: UILabel!
static let quantityFormatter: NumberFormatter = ...
static let priceFormatter: NumberFormatter = ...
weak var delegate: ItemCellDelegate?
}
Obviously, when you configure the cell, the view controller will specify the delegate for updates to the text field (or whatever):
// MARK: Public methods
extension ItemCell {
func configure(name: String, quantity: Decimal, unitPrice: Decimal, delegate: ItemCellDelegate) {
self.delegate = delegate
nameLabel.text = name
quantityTextField.text = ItemCell.quantityFormatter.string(for: quantity)
unitPriceLabel.text = ItemCell.priceFormatter.string(for: unitPrice)
}
}
And when the ItemCell gets updates, it just calls the delegate:
// MARK: Private methods
private extension ItemCell {
#IBAction func didUpdateQuantity(_ sender: UITextField) {
var quantity: Decimal?
if let text = sender.text, let value = ItemCell.quantityFormatter.number(from: text) {
quantity = value.decimalValue
}
delegate?.cell(self, didUpdateQuantity: quantity ?? 0)
}
}
Then, when the view controller configured the cell, it would supply itself as the delegate:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
let item = items[indexPath.row]
cell.configure(name: item.name, quantity: item.quantity, unitPrice: item.unitPrice, delegate: self)
return cell
}
}
And, of course, the view controller would conform to that protocol to accept updates to the text field and update the model:
extension ViewController: ItemCellDelegate {
func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
items[indexPath.row].quantity = quantity
updateTotal()
}
}
And, as you can see, if can, after updating the model, it can automatically update the total, too.
The key here is that we never use the cells as a place where we hold data. The cells are just for (a) displaying data and (b) informing the view controller if there are any updates it needs to be aware of.
We strive for a very clear separation of responsibilities, where âviewsâ are for showing and interacting with controls presently on screen and the âmodelâ contains all of our data.
The usual way to do this is to have a delegate of the cell, and make your ViewController this delegate. Whenever the value of the cell changes, send this value through the delegate so that the view controller can add up all these values.
That's the quick way.
The better way to do this is to have a datasource for your cells that keeps track of the values. This is better, because as the cells scroll on and off the screen, the datasource can keep track of the values and restore them when that index path becomes visible again.
Since the data source knows what the values are, it's trivial to sum them.
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.