TableViewController issue: won't save user input from textView - swift

Update:
so goal of this to save the text that user enters in a UITextView in cell, so that the text is saved for that particular cell number and does not duplicate, move or remove the text.
as suggested I am trying to handle the textViewdidChange func inside of the custom cell, by doing the following:
var onTextEntered: ((String?) -> ())!
func setup() {
notesTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
onTextEntered(notesTextView.text)
}
making a string that holds the text and then adding the text to the String every time textViewDidChange is called (trying to explain this to myself as we go along, so please correct me if my explanation needs it).
next in CellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TableViewNotesCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! TableViewNotesCell
cell.setup()
cell.onTextEntered = {input in
if let text = input {
self.cellText[indexPath.row] = text // here is the error (index out of range)
}
if indexPath.row < self.cellText.count {
cell.notesTextView.text = self.cellText[indexPath.row] ?? "placeholder"
}
}
return cell
}
when I do the above code, as soon as textViewDidChange is called (when i type a single letter or digit in textView) the I get the error : "Fatal error: Index out of range" on the line which I use the array of cellText[indexPath.row] = text. please help or let me know if my understanding of the process is wrong would love to learn!

You can try to save for every single edit
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// inside cellForRowAt
cell.textView.delegate = self
cell.textView.tag = indexPath.row
return cell
}
#objc func textViewDidChange(_ tex: UITextView) {
cellEndEdit[tex.tag] = tex.text!
}
class VC:UIViewController,UITextViewDelegate {
give default values for the array
var cellEndEdit = [String](repeating: "", count: numberOfRows)

Assuming, that you tableView has a variable number of cells, and all of them have a UITextView, whose content should be recorded and indexed, I would recommend to create a custom UITableViewCell, that handles the textView itself.
class CustomTableViewCell: UITableViewCell, UITextViewDelegate {
#IBOutlet weak var textView: UITextView()
var onTextEntered: ((String?) -> ()) //that's a callback
func setup() {
textView.delegate = self
}
override func textViewDidChange(textView: UITextView) {
onTextEntered(textView.text)
}
}
Since you're working with a sorted list of user inputs, you should have your array at hands, in which you can store and from which you can retrieve data. So if some data already exists, grep through your array and fill the cells that deserve it. Also define the onTextEntered callback here to tell the cell what to do, if it gets called (in your case, store the text of the UITextView in your array).
//your carrier, if you store the already existing user inputs some where, map them in here
//depending on how you calculate the number of cells, this array has to have the same size so use the same logic for this
var yourStringCarrierArray: [String?] = []
override func tableView(_ tableview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCell", for: indexPath) as! CustomTableViewCell
cell.setup()
cell.onTextEntered = { input in
if let text = input {
self.yourStringCarrierArray[indexPath.row] = text
}
if indexPath.row < yourStringCarrierArray.count {
cell.textView.text = yourStringCarrierArray[indexPath.row] ?? "placeholder string, because there's no input here so far"
}
}
I hope this will help or at least give you a new perspective, it has been a while, i coded in Swift. Feel free to ask me, if something is unclear.

Use an object to save the string value, since String in swift is value type. Here is an example:
class TestViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataArray: [CellData] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
for _ in 0..<20 {
dataArray.append(CellData())
}
}
}
extension TestViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
cell.setData(data: dataArray[indexPath.row])
return cell
}
}
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var textView: UITextView!
var data: CellData!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
textView.text = ""
textView.delegate = self
}
func setData(data: CellData) -> Void {
self.data = data
self.textView.text = data.stringValue
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension TestTableViewCell: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
self.data.stringValue = textView.text
}
}
class CellData {
var stringValue: String = ""
}

Related

Swift UITableview Reload Cell

I'm stuck with the following, any inputs would be highly appreciated.
My app has a UITableViewController with custom cells. Let's name its UITableView as TABLE VIEW-1. I am using XIB as its custom cell. Inside that xib, there is another UITableView, (TABLE VIEW-2), with another XIB as its custom cell.
My question is, How can I reload cells of (TABLE VIEW-2) from (TABLE VIEW-1) once I get data from an API in (TABLE VIEW-1). I want to use delegate and protocols to do this.
OR, What would be the correct way of performing this and how would I go about it?
Create a reference of TableView1 in the TableView2 ViewController. You can do this by
-- Open the Assistant Editor
-- Right-click and drag from your UI element (i.e. label) to the code file
-- Xcode auto inserts code for you to create the name and connection
Then call tableView2.reloadData(), where tableView2 is the name of the outlet you just created.
Create a reference of the TABLE VIEW-2 in the TABLE VIEW-1 custom cell (TABLE VIEW-1 CELL)
Create a method inside TABLE VIEW-1 CELL to reload its data alone with the TABLE VIEW-2 (Optional but useful to organize your code)
Inside the tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell of the TABLE VIEW-1 call reload function
class ViewController: UIViewController {
#IBOutlet weak var mainTable: UITableView!
let sampleData = [
(section: "News", SubSections: [
"Breaking news",
"Current Affairs",
"Local"
]) ]
override func viewDidLoad() {
super.viewDidLoad()
self.mainTable.estimatedRowHeight = 200
self.mainTable.rowHeight = UITableView.automaticDimension
let nib: UINib = UINib(nibName: String(describing: MainCell.self), bundle: Bundle.main)
self.mainTable.register(nib, forCellReuseIdentifier: String(describing: MainCell.self))
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.sampleData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MainCell = tableView.dequeueReusableCell(withIdentifier: String(describing: MainCell.self), for: indexPath) as! MainCell
let sectionData = self.sampleData[indexPath.row]
cell.setUpData(title: sectionData.section, data: sectionData.SubSections)
return cell
}
}
class MainCell: UITableViewCell {
#IBOutlet weak var sectionTitle: UILabel!
#IBOutlet weak var internalTable: UITableView!
var tableData:[String]?
override func awakeFromNib() {
super.awakeFromNib()
self.internalTable.estimatedRowHeight = 200
self.internalTable.rowHeight = UITableView.automaticDimension
let nib: UINib = UINib(nibName: String(describing: InsideCell.self), bundle: Bundle.main)
self.internalTable.register(nib, forCellReuseIdentifier: String(describing: InsideCell.self))
}
func setUpData(title: String, data:[String]) {
self.sectionTitle.text = title
self.tableData = data
self.internalTable.reloadData()
}
}
extension MainCell: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.tableData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: InsideCell = tableView.dequeueReusableCell(withIdentifier: String(describing: InsideCell.self), for: indexPath) as! InsideCell
if let subSectionData = self.tableData?[indexPath.row] {
cell.subSectionTitle.text = subSectionData
}
return cell
}
}
class InsideCell: UITableViewCell {
#IBOutlet weak var subSectionTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

Get and pass text from a TextField which is in a custom TableViewCell subclass

How can I use the text in a textField from a custom Cell?
This is the Receiving Controller:
class ShowName: UIViewController {
#IBOutlet weak var showName: UILabel!
#IBAction func unwindToShowNameData(_ unwindSegue: UIStoryboardSegue) {
let sourceViewController = unwindSegue.source as! enterName
showName.text = sourceViewController.name
}
#IBAction func unwindToShowName(_ unwindSegue: UIStoryboardSegue) {
}
}
This is the Sending Controller:
class enterName: UIViewController, UITableViewDataSource, UITableViewDelegate {
var name: String?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! nameCell
return cell
}
#IBAction func clickSave(_ sender: UIBarButtonItem) {
let cell = nameCell()
name = cell.nameText.text
performSegue(withIdentifier: "passData", sender: self)
}
}
This is the class Cell with TextField:
class NameCell: UITableViewCell {
#IBOutlet weak var nameText: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Any ideas?
Actually that's the same solution as my suggestion in your previous question.
Add a callback in the cell and call it when the text field delegate method is called. Don't forget to connect the text field delegate in Interface Builder.
And please name classes and structs always with starting uppercase letter
class NameCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var nameText: UITextField!
var callback : ((UITableViewCell, String) -> Void)?
func textFieldDidEndEditing(_ textField: UITextField) {
callback?(self, nameText.text)
}
}
If you have more than one row you need to declare a data source array to maintain the values of the text fields. This is an example for 4 rows
var values = ["", "", "", ""]
These are the data source methods, in cellForRow the callback updates the model
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! NameCell
cell.nameText.text = values[indexPath.row]
cell.callback = { [unowned self] cCell, cName in
let currentIndexPath = tableView.indexPath(for: cCell)!
self.values[currentIndexPath.row] = cName
}
return cell
}
The benefit is you are independent of the cells in the clickSave method. Get the values from the data source array. And you can pass the name as sender parameter and hand it over in prepare(for segue
#IBAction func clickSave(_ sender: UIBarButtonItem) {
let name = values[0]
performSegue(withIdentifier: "passData", sender: name)
}

My TableView's not changed after reload data

I wrote a class that let me create multiple tableView simply. When I call this class for the first time, everything work well. But when I change some data, and reload the table, nothing changed.
Sample code:
class TestViewController: UIViewController {
var arrData = ["a","b","c"]
var myTableView: MyTableView?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView = MyTableView(table: tableView, data: arrData)
}
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.tableView.reloadData() //=> Not change anything
}
}
class MyTableView: NSObject, UITableViewDataSource {
var tableView: UITableView
var data: Array<String>
init(table: UITableView, data: Array<String>) {
self.data = data
self.tableView = table
super.init()
self.tableView.dataSource = self
self.tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "myCell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.textLabel!.text = self.data[indexPath.row]
return cell
}
}
class MyTableViewCell : UITableViewCell {
//something here
}
When the view was loaded, the table has 3 rows: a,b,c. When I tap the button, nothing changed (expected: d,e,f)
Please help me!
Swift arrays are copied by value so the line self.data = data will take a copy of your array. Later changing the array contents of the source will not be reflected in the copy in your MyTableView.
You'll need to pass the array over again and take a second copy to update the table, e.g. write a method in MyTableView similar to the following:-
func setNewValues(data: Array<String>)
{
self.data = data
self.tableView.reloadData()
}
and call that from your buttonTapped function, i.e.:
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.setNewValues(data: arrData)
}
Be careful with the force-unwrapped myTableView though - I'd replace that '!' with '?'.

How do I get text fields inside a table view to talk to each other?

Specifically for a calculator app. I want to type in the textfield in the first row and have that change text in the other rows. Really, type in any row and change the value of the other rows.
I know I should be working with ints but I was going to worry about type converting later.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
//label and text_field come from CustomTableViewCell.swift
cell.label.text = array[indexPath.row]
cell.text_field.text = array_values[indexPath.row]
return cell
}
This is inside my tableViewController class and thats the func that generates the rows from the arrays.
Im after something like when you type in a text field, hit a button, and it updates some label, but where theres no button and text fields update other text fields as the user types. Inside a table view.
Heres the rest of the code.
import UIKit
class TableViewController: UITableViewController {
#IBOutlet var table_view: UITableView!
var array = [String]()
var array_values = [String]()
override func viewDidLoad() {
super.viewDidLoad()
array = ["1", "2", "3", "4", "5"]
array_values = ["", "", "", "", ""]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
cell.label.text = array[indexPath.row]
cell.text_field.text = array_values[indexPath.row]
return cell
}
}
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var text_field: UITextField!
#IBOutlet weak var label: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

creating custom tableview cells in swift

I have a custom cell class with a couple of IBOutlets. I have added the class to the storyboard. I have connected all my outlets. my cellForRowAtIndexPath function looks like this:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as SwipeableCell
cell.mainTextLabel.text = self.venueService.mainCategoriesArray()[indexPath.row]
return cell
}
Here is my custom cell class:
class SwipeableCell: UITableViewCell {
#IBOutlet var option1: UIButton
#IBOutlet var option2: UIButton
#IBOutlet var topLayerView : UIView
#IBOutlet var mainTextLabel : UILabel
#IBOutlet var categoryIcon : UIImageView
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
When I run the app, all my cell are empty. I have logged out self.venueService.mainCategoriesArray() and it contains all the correct strings. I have also tried putting an actual string equal to the label, and that produces the same result.
What am I missing? Any help is appreciated.
Custom Table View Cell Example
Tested with Xcode 9 (edit also tested on 11 / 12 Beta 2) and Swift 4 (edit: also tested on 5.2)
The asker of the original question has solved their problem. I am adding this answer as a mini self contained example project for others who are trying to do the same thing.
The finished project should look like this:
Create a new project
It can be just a Single View Application.
Add the code
Add a new Swift file to your project. Name it MyCustomCell.swift. This class will hold the outlets for the views that you add to your cell in the storyboard.
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myView: UIView!
#IBOutlet weak var myCellLabel: UILabel!
}
We will connect these outlets later.
Open ViewController.swift and make sure you have the following content:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
// These are the colors of the square views in our table view cells.
// In a real project you might use UIImages.
let colors = [UIColor.blue, UIColor.yellow, UIColor.magenta, UIColor.red, UIColor.brown]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myView.backgroundColor = self.colors[indexPath.row]
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Setup the storyboard
Add a Table View to your view controller and use auto layout to pin it to the four sides of the View Controller. Then drag a Table View Cell onto the Table View. And then drag a View and a Label onto the Prototype cell. (You may need to select the Table View Cell and manually set the Row Height to something taller in the Size inspector so that you have more room to work with.) Use auto layout to fix the View and the Label how you want them arranged within the content view of the Table View Cell. For example, I made my View be 100x100.
Other IB settings
Custom class name and Identifier
Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.
Hook Up the Outlets
Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
Do the same for the View and the Label in your Prototype cell to the myView and myCellLabel variables in the MyCustomCell class.
Finished
That's it. You should be able to run your project now.
Notes
The colored views that I used here could be replaced with anything. An obvious example would be a UIImageView.
If you are just trying to get a TableView to work, see this even more basic example.
If you need a Table View with variable cell heights, see this example.
This is for who are working custom cell with .xib
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let identifier = "Custom"
var cell: CustomCell! = tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomCel
if cell == nil {
tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: identifier)
cell =tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomCell
}return cell}
I have the same problem.
Generally what I did is the same as you.
class dynamicCell: UITableViewCell {
#IBOutlet var testLabel : UILabel
init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
and in the uitableviewcell method:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell :dynamicCell = tableView.dequeueReusableCellWithIdentifier("cell") as dynamicCell
cell.testLabel.text = "so sad"
println(cell.testLabel)
return cell;
}
and yeah the tableview shows nothing! But guess what, it actually shows something...because the log I get from the println(cell.testLabel) shows that all the Labels are actually displayed out.
BUT! their Frames is strange, which have something like this:
frame = (0 -21; 42 21);
so it has a (0,-21) as (x,y), so that means the label just appears at somewhere outside the bound of the cell.
so I try to add adjust the frame manually like this:
cell.testLabel.frame = CGRectMake(10, 10, 42, 21)
and sadly, it doesn't work.
---------------update after 10 min -----------------
I DID IT.
so, it seems that the problem comes from the Size Classes.
Click on your .storyboard file and go to the File Inspector Tab
UNCHECK THE Size Classes checkbox
and finally, my "so sad"Label comes out!
Thanks for all the different suggestions, but I finally figured it out. The custom class was set up correctly. All I needed to do, was in the storyboard where I choose the custom class: remove it, and select it again. It doesn't make much sense, but that ended up working for me.
Last Updated Version is with xCode 6.1
class StampInfoTableViewCell: UITableViewCell{
#IBOutlet weak var stampDate: UILabel!
#IBOutlet weak var numberText: UILabel!
override init?(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init(coder aDecoder: NSCoder) {
//fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Details
Xcode Version 10.2.1 (10E1001), Swift 5
Solution
import UIKit
// MARK: - IdentifiableCell protocol will generate cell identifier based on the class name
protocol Identifiable: class {}
extension Identifiable { static var identifier: String { return "\(self)"} }
// MARK: - Functions which will use a cell class (conforming Identifiable protocol) to `dequeueReusableCell`
extension UITableView {
typealias IdentifiableCell = UITableViewCell & Identifiable
func register<T: IdentifiableCell>(class: T.Type) { register(T.self, forCellReuseIdentifier: T.identifier) }
func register(classes: [Identifiable.Type]) { classes.forEach { register($0.self, forCellReuseIdentifier: $0.identifier) } }
func dequeueReusableCell<T: IdentifiableCell>(aClass: T.Type, initital closure: ((T) -> Void)?) -> UITableViewCell {
guard let cell = dequeueReusableCell(withIdentifier: T.identifier) as? T else { return UITableViewCell() }
closure?(cell)
return cell
}
func dequeueReusableCell<T: IdentifiableCell>(aClass: T.Type, for indexPath: IndexPath, initital closure: ((T) -> Void)?) -> UITableViewCell {
guard let cell = dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as? T else { return UITableViewCell() }
closure?(cell)
return cell
}
}
extension Array where Element == UITableViewCell.Type {
var onlyIdentifiables: [Identifiable.Type] { return compactMap { $0 as? Identifiable.Type } }
}
Usage
// Define cells classes
class TableViewCell1: UITableViewCell, Identifiable { /*....*/ }
class TableViewCell2: TableViewCell1 { /*....*/ }
// .....
// Register cells
tableView.register(classes: [TableViewCell1.self, TableViewCell2.self]. onlyIdentifiables)
// Create/Reuse cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.row % 2) == 0 {
return tableView.dequeueReusableCell(aClass: TableViewCell1.self, for: indexPath) { cell in
// ....
}
} else {
return tableView.dequeueReusableCell(aClass: TableViewCell2.self, for: indexPath) { cell in
// ...
}
}
}
Full Sample
Do not forget to add the solution code here
import UIKit
class ViewController: UIViewController {
private weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
}
// MARK: - Setup(init) subviews
extension ViewController {
private func setupTableView() {
let tableView = UITableView()
view.addSubview(tableView)
self.tableView = tableView
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.register(classes: [TableViewCell1.self, TableViewCell2.self, TableViewCell3.self].onlyIdentifiables)
tableView.dataSource = self
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch (indexPath.row % 3) {
case 0:
return tableView.dequeueReusableCell(aClass: TableViewCell1.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
case 1:
return tableView.dequeueReusableCell(aClass: TableViewCell2.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
default:
return tableView.dequeueReusableCell(aClass: TableViewCell3.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
}
}
}
Results
Uncheck "Size Classes" checkbox works for me as well, but you could also add the missing constraints in the interface builder. Just use the built-in function if you don't want to add the constraints on your own. Using constraints is - in my opinion - the better way because the layout is independent from the device (iPhone or iPad).
It is Purely swift notation an working for me
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cellIdentifier:String = "CustomFields"
var cell:CustomCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CustomCell
if (cell == nil)
{
var nib:Array = NSBundle.mainBundle().loadNibNamed("CustomCell", owner: self, options: nil)
cell = nib[0] as? CustomCell
}
return cell!
}
[1] First Design your tableview cell in StoryBoard.
[2] Put below table view delegate method
//MARK: - Tableview Delegate Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return <“Your Array”>
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
var totalHeight : CGFloat = <cell name>.<label name>.frame.origin.y
totalHeight += UpdateRowHeight(<cell name>.<label name>, textToAdd: <your array>[indexPath.row])
return totalHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell : <cell name>! = tableView.dequeueReusableCellWithIdentifier(“<cell identifier>”, forIndexPath: indexPath) as! CCell_VideoCall
if(cell == nil)
{
cell = NSBundle.mainBundle().loadNibNamed("<cell identifier>", owner: self, options: nil)[0] as! <cell name>;
}
<cell name>.<label name>.text = <your array>[indexPath.row] as? String
return cell as <cell name>
}
//MARK: - Custom Methods
func UpdateRowHeight ( ViewToAdd : UILabel , textToAdd : AnyObject ) -> CGFloat{
var actualHeight : CGFloat = ViewToAdd.frame.size.height
if let strName : String? = (textToAdd as? String)
where !strName!.isEmpty
{
actualHeight = heightForView1(strName!, font: ViewToAdd.font, width: ViewToAdd.frame.size.width, DesignTimeHeight: actualHeight )
}
return actualHeight
}
Set tag for imageview and label in cell
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.tableData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("imagedataCell", forIndexPath: indexPath) as! UITableViewCell
let rowData = self.tableData[indexPath.row] as! NSDictionary
let urlString = rowData["artworkUrl60"] as? String
// Create an NSURL instance from the String URL we get from the API
let imgURL = NSURL(string: urlString!)
// Get the formatted price string for display in the subtitle
let formattedPrice = rowData["formattedPrice"] as? String
// Download an NSData representation of the image at the URL
let imgData = NSData(contentsOfURL: imgURL!)
(cell.contentView.viewWithTag(1) as! UIImageView).image = UIImage(data: imgData!)
(cell.contentView.viewWithTag(2) as! UILabel).text = rowData["trackName"] as? String
return cell
}
OR
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "imagedataCell")
if let rowData: NSDictionary = self.tableData[indexPath.row] as? NSDictionary,
urlString = rowData["artworkUrl60"] as? String,
imgURL = NSURL(string: urlString),
formattedPrice = rowData["formattedPrice"] as? String,
imgData = NSData(contentsOfURL: imgURL),
trackName = rowData["trackName"] as? String {
cell.detailTextLabel?.text = formattedPrice
cell.imageView?.image = UIImage(data: imgData)
cell.textLabel?.text = trackName
}
return cell
}
see also TableImage loader from github
The actual Apple reference documentation is quite comprehensive
https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/CreateATableView.html#//apple_ref/doc/uid/TP40015214-CH8-SW2
Scroll down until you see this part