I need index path for both section and row in tableview, I got a index for row but not for the section - ios

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellrow", for: indexPath) as! TableViewCellrow
cell.imgView.image = UIImage(named: namearr[indexPath.section].1[indexPath.row])
cell.lblName.text = namearr[indexPath.section].1[indexPath.row]
cell.btnCross.tag = indexPath.row
cell.btnCross.addTarget(self, action: #selector(cutt), for: .touchUpInside)

If I got it right, you wanna store both row and section in button tag so it can be accessed in cutt function.
You can do it like this:
cell.btnCross.tag = (indexPath.section << 16) | indexPath.row
And then retrieve it in cutt func like this:
let section = button.tag >> 16
let row = button.tag & ((1 << 16) - 1)
Also note, that storing data in UIView tag is not the best solution in terms of performance.
UIKit implements tags using objc_get/setAssociatedObject(), meaning that every time you set or get a tag, you’re doing a dictionary lookup. Check out more here
An other option, which is I'd say is better in terms of architecture too, is moving touch handling into the Cell, like this:
class TableViewCellrow: UITableViewCell {
var btnCrossTapHandler: (() -> Void)?
#IBOutlet weak var btnCross: UIButton!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initialize()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initialize()
}
private func initialize() {
btnCross.addTarget(self, action: #selector(cutt), for: .touchUpInside)
}
#objc func cutt() {
btnCrossTapHandler?()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.btnCrossTapHandler = { [weak self] in
// you have indexPath here
}
...
}

Don't use tuples in a datasource array. This is very bad practice.
Create a custom struct like
struct Section {
let title : String
let items : [String]
}
let namearr = [Section(title: "Vegtable", items: ["cauliflower","radish"]),
Section(title: "Fruit", items: ["grapres","mango"]),
Section(title: "FLower", items: ["lily","marigold"])]
The capitalized names can be simply created with
let section = namearr[indexPath.section]
let name = section.items[indexPath.row]
let capName = name.capitalized
For the tag I recommend a calculation section * 100 + row
func tag(for indexPath : IndexPath) -> Int {
return indexPath.section * 100 + indexPath.row
}
and the other way round
func indexPath(for tag : Int) -> IndexPath {
return IndexPath(row: tag % 100, section: tag / 100)
}

Related

How to handle multiple buttons in a custom UITableViewCell?

I have a custom UITableViewCell which has 2 buttons (for incrementing and decrementing) and a count label in it. What I want to achieve is to update countLabel appropriately when subtractButton or addButton is tapped.
My custom cell class:
class ItemOptionCell: UITableViewCell {
private var count = 0
private var countLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 0
label.text = "0"
label.adjustsFontSizeToFitWidth = true
return label
}()
private let subtractButton: UIButton = {
let subButton = UIButton(type: .system)
subButton.setTitle("-", for: .normal)
subButton.addTarget(self, action: #selector(decreaseItemCount), for: .touchUpInside)
return subButton
}()
private let addButton: UIButton = {
let addButton = UIButton(type: .system)
addButton.setTitle("+", for: .normal)
addButton.addTarget(self, action: #selector(increaseItemCount), for: .touchUpInside)
return addButton
}()
// contains subtract, add buttons and item count
private var operationsStackView = UIStackView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .white
configureOperationsStackView()
}
func set(itemOption: ItemOption) {
itemLabel.text = itemOption.title
}
private func configureOperationsStackView() {
addSubview(operationsStackView)
// code for autolayout
}
#objc private func decreaseItemCount() {
if count > 0 {
count -= 1
}
updateCountLabel()
}
#objc private func increaseItemCount() {
count += 1
updateCountLabel()
}
private func updateCountLabel() {
countLabel.text = String(count)
}
Part of ViewController for handling table view delegates:
extension ItemOptionsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "Header #1"
label.backgroundColor = .orange
return label
}
func numberOfSections(in tableView: UITableView) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId) as! ItemOptionCell
let option = options[indexPath.section][indexPath.row]
cell.set(itemOption: ItemOption(title: option))
cell.selectionStyle = .none
return cell
}
}
All the forums I looked up only describe how to handle single buttons.
P.s. I read about assigning tags to buttons but found out that it's not a recommended way as when row count changes managing tags becomes problematic. Therefore, if possible, recommend a way with delegates or closures.
Use delegate for this purpose
protocol ItemOptionCellDelegate: AnyObject {
func didDecreaseItemTapped(in cell: ItemOptionCell)
func didIncreaseItemCount(in cell: ItemOptionCell)
}
class ItemOptionCell: UITableViewCell {
weak var delagate: ItemOptionCellDelegate?
...
private func decreaseItemCount() {
delegate?.didDecreaseItemTapped(in: self)
}
private func increaseItemCount() {
delegate?.didIncreaseItemCount(in: self)
}
in your ViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId) as! ItemOptionCell
let option = options[indexPath.section][indexPath.row]
cell.set(itemOption: ItemOption(title: option))
cell.selectionStyle = .none
cell.delegate = self
cell.countLabel.text = //some value
return cell
}
extension ItemOptionsViewController: ItemOptionCellDelegate {
func didDecreaseItemTapped(in cell: ItemOptionCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let option = options[indexPath.section][indexPath.row]
//do some stuff with your data, then reload table and set need value for count label.
}
func didIncreaseItemCount(in cell: ItemOptionCell) { ... }
Also, DON'T update countLabel inside cell implementation, basically you have to set value count from your model, for example in ViewController in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell func (in case you use MVC approach)
Forget about tags.
Create gesture recognisers in your cellForRowAt and assign them to the buttons:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
//Create gesture
//Create two of these: one will be for the add button and one for the minus one. Attach to eachh gesture a different function.
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
//Add the two different gestures to both of the buttons
cell.myButton.addGestureRecognizer(tap)
...
}
//Create two of these: one to add and one to subtract
#objc func tapped(){
print("ok")
//Perform your action here
}
Plus, when it comes to adding the buttons in the tableViewCell custom class, call contentView.addSubview(myButton) instead of addSubview(myButton).
Oh and obviously, remove all the stuff in your custom class where you add targets to buttons and stuff like that, only create and add the objects to the cell as I said before.
Edit:
Set your buttons as follows:
//Do this for both buttons
private let subtractButton: UIButton = {
let subButton = UIButton(type: .system)
subButton.setTitle("-", for: .normal)
return subButton
}()

Scrolling tableview causes checkmark to disappear | addressing reusable cells in Swift 5

I use the following to mark rows in a tableview as either marked with a checkmark or unselected with no checkmark. The issue that I have stumbled on is when scrolling the tableView seems to reload and cause the checkmark to disappear.
I understand this is caused by reusable cells,
Is there an easy fix I can implement into the code below?
class CheckableTableViewCell: UITableViewCell {
var handler: ((Bool)->())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
handler?(selected)
}
}
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self](selected) in
selected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let stringText = "\(item.code)"
cell.textLabel!.text = stringText
return cell
}
UPDATE:
struct Section {
let name : String
var items : [Portfolios]
}
struct Portfolios: Decodable {
let code: String
var isSelected: Bool
enum CodingKeys : String, CodingKey {
case code
}
}
You need to create your data model having a property called isSelected: Bool then use this to decide when a row should be selected or not. Note that you have to toggle this property every time didSelectRowAt(indexPath:) is triggered.
Example
// Declare your model with one of the property called isSelected
class MyModel {
var isSelected: Bool
init(isSelected: Bool) {
self.isSelected = isSelected
}
}
class TableViewController: UITableViewController {
// Dummy data note that it contains array of "MyModel" which have the "isSelected" property.
private var myModels: [MyModel] = [MyModel(isSelected: false),MyModel(isSelected: true),MyModel(isSelected: true),MyModel(isSelected: false) ]
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self](selected) in
/// Note that I am using selected Property here
myModels[indexPath.row].isSelected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let stringText = "\(item.code)"
cell.textLabel!.text = stringText
return cell
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedIndexPath = indexPath
myModels[selectedIndexPath.row].isSelected = true
self.tableView.reloadRows(at: [selectedIndexPath], with: .none)
}
}

Custom uitableview cell not showing all text labels

I am trying to show an object from firebase in a custom cell. It has three text labels. When, I run my code it only shows one line of text per cell, instead of three, in the tableview. It only returns which ever text label is first.
Here is my code for my Class object:
class Class: NSObject {
var date_clasname: String?
var teacher: String?
var room_number: String?
init(dictionary: [String: Any]) {
self.date_clasname = dictionary["date_clasname"] as? String ?? ""
self.teacher = dictionary["teacher"] as? String ?? ""
self.room_number = dictionary["room_number"] as? String ?? ""
}
}
Here is my code for my tableview class:
class classes_test_TableViewController: UITableViewController {
let cellId = "cellId"
var users = [Class]()
override func viewDidLoad() {
super.viewDidLoad()
//navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
fetchClass()
}
func fetchClass() {
// guard let uid = ?.user.uid
// else{return}
//let userID = Auth.auth().currentUser!.uid
Database.database().reference().child("Science").observe(.childAdded, with: { (snapshot) in
//print(userID)
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = Class(dictionary: dictionary)
self.users.append(user)
print(snapshot)
//this will crash because of background thread, so lets use dispatch_async to fix
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
#objc func handleCancel() {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let use a hack for now, we actually need to dequeue our cells for memory efficiency
// let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let Class = users[indexPath.row]
cell.textLabel?.text = Class.date_clasname
cell.textLabel?.text = Class.teacher
cell.textLabel?.text = Class.room_number
return cell
}
}
class UserCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier:
String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is my database structure:
"Science" : {
"-Lgxm6qJhzI2IIG4uary" : {
"date_clasname" : "f",
"room_number" : "d",
"teacher" : "f"
}
The cell is suppose to show all three strings but only shows one.
You are using the standard UITableViewCell and you assign all three values to the same label.
You have to cast the cell to the custom cell and assign the values to the custom labels
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let use a hack for now, we actually need to dequeue our cells for memory efficiency
// let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let user = users[indexPath.row]
cell.nameLabel?.text = user.date_clasname
cell.teacherLabel?.text = user.teacher
cell.roomLabel?.text = user.room_number
return cell
}
Replace nameLabel, teacherLabel and roomLabel with the real property names.
And please conform to the naming convention and name variables lowerCamelCased for example dateClasname and roomNumber

How to preserve user input in UITableViewCell before dequeue

I'm creating an application in which I need the users to fill out a number of inputs in a UITableViewCell, kinda like a form. When the user taps on the done button, I need to collect those inputs so I can run some calculations and output them on another view controller
Here is the method I used to collect those inputs:
func doneButtonTapped() {
var dict = [String: Any]()
for rows in 0...TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected).count {
let ip = IndexPath(row: rows, section: 0)
let cells = tableView.cellForRow(at: ip)
if let numericCell = cells as? NumericInputTableViewCell {
if let text = numericCell.userInputTextField.text {
dict[numericCell.numericTitleLabel.text!] = text
}
} else if let booleanCell = cells as? BooleanInputTableViewCell {
let booleanSelection = booleanCell.booleanToggleSwitch.isOn
dict[booleanCell.booleanTitleLabel.text!] = booleanSelection
}
}
let calculator = Calculator(userInputDictionary: dict, ceiling_type: node.ceilingSelected)
}
The problem I'm having is when the cell is out of view, the user's input is cleared from the memory. Here are two screenshots to illustrate my point:
As you can see, when all the cells appears, the done button managed to store all the inputs from the user, evidently from the console print. However, if the cells are out of view, the inputs from area/m2 are set to nil:
The solution that came to mind was I shouldn't use a dequeue-able cell as I do want the cell to be in memory when it is out-of-view, but many of the stackover community strong against this practice. How should I solve this problem? Thanks!
UPDATE
Code for cellForRow(at: IndexPath)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let node = node else {
return UITableViewCell()
}
let cellArray = TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected)
switch cellArray[indexPath.row].cellType {
case .numericInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "numericCell", for: indexPath) as! NumericInputTableViewCell
cell.numericTitleLabel.text = cellArray[indexPath.row].title
return cell
case .booleanInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "booleanCell", for: indexPath) as! BooleanInputTableViewCell
cell.booleanTitleLabel.text = cellArray[indexPath.row].title
return cell
}
}
}
My two custom cells
NumericInputTableViewCell
class NumericInputTableViewCell: UITableViewCell {
#IBOutlet weak var numericTitleLabel: UILabel!
#IBOutlet weak var userInputTextField: UITextField!
}
BooleanInputTableViewCell
class BooleanInputTableViewCell: UITableViewCell {
#IBOutlet weak var booleanTitleLabel: UILabel!
#IBOutlet weak var booleanToggleSwitch: UISwitch!
}
Any takers?
I agree with the other contributors. The cells should not be used for data storage. You should consider another approach (like the one HMHero suggests).
But, as your question was also about how to access a UITableViewCell before it is removed, there is a method in UITableViewDelegate that you can use for that:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// do something with the cell before it gets deallocated
}
This method tells the delegate that the specified cell was removed from the table. So it gives a last chance to do something with that cell before it disappears.
Because of table view reuses its cells, usually, it's not a good idea if your data depends on some components from the table view cell. Rather, it should be the other way around. Your table view data always drive it's table view cell's component even before any user input data is provided in your case.
Initial Data - your should already have somewhere in your code. I created my own from your provided code
let data = CellData()
data.title = "Troffer Light Fittin"
data.value = false
let data2 = CellData()
data2.title = "Length Drop"
data2.value = "0"
cellData.append(data)
cellData.append(data2)
Example
enum CellType {
case numericInput, booleanInput
}
class CellData {
var title: String?
var value: Any?
var cellType: CellType {
if let _ = value as? Bool {
return .booleanInput
} else {
return .numericInput
}
}
}
protocol DataCellDelegate: class {
func didChangeCellData(_ cell: UITableViewCell)
}
class DataTableViewCell: UITableViewCell {
var data: CellData?
weak var delegate: DataCellDelegate?
}
class NumericInputTableViewCell: DataTableViewCell {
let userInputTextField: UITextField = UITextField()
override var data: CellData? {
didSet {
textLabel?.text = data?.title
if let value = data?.value as? String {
userInputTextField.text = value
}
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
userInputTextField.addTarget(self, action: #selector(textDidChange(_:)), for: .editingChanged)
contentView.addSubview(userInputTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func textDidChange(_ textField: UITextField) {
//update data and let the delegate know data is updated
data?.value = textField.text
delegate?.didChangeCellData(self)
}
//Disregard this part
override func layoutSubviews() {
super.layoutSubviews()
textLabel?.frame.size.height = bounds.size.height / 2
userInputTextField.frame = CGRect(x: (textLabel?.frame.origin.x ?? 10), y: bounds.size.height / 2, width: bounds.size.width - (textLabel?.frame.origin.x ?? 10), height: bounds.size.height / 2)
}
}
class BooleanInputTableViewCell: DataTableViewCell {
override var data: CellData? {
didSet {
textLabel?.text = data?.title
if let value = data?.value as? Bool {
booleanToggleSwitch.isOn = value
}
}
}
let booleanToggleSwitch = UISwitch(frame: .zero)
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
booleanToggleSwitch.addTarget(self, action: #selector(toggled), for: .valueChanged)
booleanToggleSwitch.isOn = true
accessoryView = booleanToggleSwitch
accessoryType = .none
selectionStyle = .none
}
func toggled() {
//update data and let the delegate know data is updated
data?.value = booleanToggleSwitch.isOn
delegate?.didChangeCellData(self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In View Controller, you should update your original data source so when you scroll the table view, the data source privide right infomation.
func didChangeCellData(_ cell: UITableViewCell) {
if let cell = cell as? DataTableViewCell {
for data in cellData {
if let title = data.title, let titlePassed = cell.data?.title, title == titlePassed {
data.value = cell.data?.value
}
}
}
for data in cellData {
print("\(data.title) \(data.value)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = cellData[indexPath.row]
let cell: DataTableViewCell
if data.cellType == .booleanInput {
cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(BooleanInputTableViewCell.self), for: indexPath) as! BooleanInputTableViewCell
} else {
cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(NumericInputTableViewCell.self), for: indexPath) as! NumericInputTableViewCell
}
cell.data = cellData[indexPath.row]
cell.delegate = self
return cell
}
In short, try to have a single data source for table view and use the delegate to pass the updated data in the cell back to the data source.
Please disregard anything that has to do with layout. I didn't use the storyboard to test your requirements.

Designing a UITableView/Cell - iOS

I'm designing a UITableView using subviews to populate the reusable cell of it, and I wish some opinion about that.
As I had tested, it works well. But, I don't know if it is a good solution.
The scenario is: I have a tableview with different kind of cells (layouts). When I was designing, it grows fast (my controller code), as I had to register a lot of cell and handle cellForRow. Then I come with that idea, to instantiate different subviews for one unique reusable cell and use a 'Presenter' to handle delegate/datasource. You think is that a problem? And is that a good approach?
Thanks in advance!
Ps.: sorry for any english error!
EDITED:
Here is the session in project followed by de codes:
Codes at:
OrderDetailCell
class OrderDetailCell: UITableViewCell {
//MARK: Outlets
#IBOutlet weak var cellHeight: NSLayoutConstraint!
#IBOutlet weak var viewContent: UIView!
//Variables
var didUpdateLayout = false
internal func setupLayoutWith(view: UIView){
cellHeight.constant = view.frame.height
viewContent.frame = view.frame
viewContent.addSubview(view)
updateConstraints()
layoutIfNeeded()
didUpdateLayout = true
}
}
OrderDetailSubview
class OrderDetailSubview: UIView {
var type: OrderDetailsSubViewType?
var height: CGFloat = 1
class func instanceFromNib(withType type: OrderDetailsSubViewType) -> OrderDetailSubview {
let view = UINib(nibName: type.rawValue, bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! OrderDetailSubview
switch type {
case .OrderDetailSubviewStatus:
view.height = 258
case .OrderDetailSubViewItem:
view.height = 129
case .OrderDetailSubViewStoreInformation:
view.height = 317
case .OrderDetailSubViewEvaluation:
view.height = 150
}
view.updateConstraints()
view.layoutIfNeeded()
return view
}
}
OrderDetailPresenter
enum OrderDetailsSubViewType: String {
case OrderDetailSubviewStatus = "OrderDetailSubviewStatus",
OrderDetailSubViewItem = "OrderDetailSubViewItem",
OrderDetailSubViewStoreInformation = "OrderDetailSubViewStoreInformation",
OrderDetailSubViewEvaluation = "OrderDetailSubViewEvaluation"
static let types = [OrderDetailSubviewStatus, OrderDetailSubViewItem, OrderDetailSubViewStoreInformation, OrderDetailSubViewEvaluation]
}
class OrderDetailPresenter {
//Constants
let numberOfSections = 4
//Variables
// var order: Order?
func setup(reusableCell: UITableViewCell, forRowInSection section: Int) -> OrderDetailCell {
let cell = reusableCell as! OrderDetailCell
for sub in cell.viewContent.subviews {
sub.removeFromSuperview()
}
let subView = OrderDetailSubview.instanceFromNib(withType: OrderDetailsSubViewType.types[section])
cell.setupLayoutWith(view: subView)
return cell
}
func numberOfRowsForSection(_ section: Int) -> Int {
switch section {
case 1:
//TODO: count de offerList
return 4
default:
return 1
}
}
}
OrderDetailViewController
class OrderDetailViewController: BaseViewController {
//MARK: Outlets
#IBOutlet weak var tableView: UITableView!
var presenter = OrderDetailPresenter()
override func setupView() {
setupTableView()
}
}
extension OrderDetailViewController: UITableViewDataSource, UITableViewDelegate {
internal func setupTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 600
tableView.rowHeight = UITableViewAutomaticDimension
tableView.register(UINib(nibName: "OrderDetailCell", bundle: nil), forCellReuseIdentifier: "OrderDetailCell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return presenter.numberOfSections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.numberOfRowsForSection(section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reusableCell = tableView.dequeueReusableCell(withIdentifier: "OrderDetailCell") as! OrderDetailCell
let cell = presenter.setup(reusableCell: reusableCell, forRowInSection: indexPath.section)
return cell
}
}
*Sorry for indentation here...
Thats it! What you think?
Here you want to have multiple UITableViewCell subclasses that implement the different layouts that you want, and then select the relevant one in you table view data source.
class Cell1: UITableViewCell {
let label = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(label)
}
... whatever other setup/layout you need to do in the class ...
}
class Cell2: UITableViewCell {
let imageView = UIImageView()
override init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(imageView)
}
... whatever other setup/layout you need to do in the class ...
}
Then in your view controller
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(Cell1.self, forCellReuseIdentifier: "cell1Identifier")
tableView.register(Cell2.self, forCellReuseIdentifier: "cell2Identifier")
}
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 { // just alternating rows for example
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1Identifier", for: indexPath) as! Cell1
// set data on cell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2Identifier", for: indexPath) as! Cell2
// set data on cell
return cell
}
}
So this is just an example, but is using two different cell subclasses for alternating rows in the table view.
let dynamicCellID: String = "dynamicCellID" //One Cell ID for resuse
class dynamicCell: UITableViewCell {
var sub: UIView // you just need to specify the subview
init(sub: UIView) {
self.sub = sub
super.init(style: .default, reuseIdentifier: dynamicCellID)
self.addSubview(sub)
self.sub.frame = CGRect(x: 0, y: 0, width: sub.frame.width, height: sub.frame.height)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And you need to create a views array the give that view to every cell in delegate
let views: [UIView] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return views.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let v = views[indexPath.row]
return dynamicCell(sub: v)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let v = views[indexPath.row]
return v.frame.height + 10 //offset is 10 point
}

Resources