I a tableView class that displays a text in cells. I need to be able to touch/click text in a cell and create an action (display the next table view) based on the text in the cell clicked. Class is as follows:
import UIKit
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
let textCellIdentifier = "TextCell"
let catRet = XnYCategories.mainCats("sport")
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// MARK: UITextFieldDelegate Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return catRet.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! UITableViewCell
let row = indexPath.row
cell.textLabel?.text = catRet[row]
return cell
}
// MARK: UITableViewDelegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println(catRet[row])
}
}
I would like to add upon Linus answer, as it has deprecated in latest Swift addition.
Swift 4:
In the myCell add this Recogniser,
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(yourVC.yourfuncName))
myCell.addGestureRecognizer(tapGesture)
In the same VC, implement your function,
func yourfuncName(){
//Do whatever you want here
}
If you have a hard-coded amount of cells, you can create a UIGestureRecognizer and add it to the cell created:
let tapGesture = UITapGestureRecognizer(target: self, action: "tapped:")
myCell.addGestureRecognizer(tapGesture)
In it's target you can do whatever you want to do.
You can get your cell using
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell
To get your text you could use
var selectedText = currentCell.textLabel?.text
Related
I am trying to add an action to my "like" button. So that when the user taps the heart UIButton in a cell, the heart in the cell they tapped updates to a pink heart showing that they liked it. But instead it likes the heart they tapped and another random heart in a different cell that they did not interact with. I have been on this all day and any help would be grateful. For Example, if I like/tap my heart UIButton the buttons image I tapped updates, but when I scroll down another random heart updates from that same first cell button tap.
Also When I scroll and the cell leaves view and scroll back up the image returns back to unlike and other like buttons become liked.
Keep a data model for your buttons state
Try with the below code
struct TableModel {
var isLiked: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataSource: [TableModel] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
dataSource = Array(repeating: TableModel(isLiked: false), count: 20)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataSource.count
}
#objc func buttonSelected(_ sender: UIButton) {
dataSource[sender.tag].isLiked = !dataSource[sender.tag].isLiked
let indexPath = IndexPath(row: sender.tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.likeBtn.tag = indexPath.row
cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
//set unlike image
}
return cell
}
}
Currently, you have a hardcoded number of rows, but anyway you will need to have a data source with data models. When you press the button, you have to save the state of the button of a specific row. I would recommend you create a model first.
Here I provided an easy (but flexible enough) way how to do this. I haven't debugged it, but it should work and you can see the idea. I hope this would be helpful.
Create Cell Model
struct CellViewModel {
let title: String
var isLiked: Bool
// Add other properties you need for the cell, image, etc.
}
Update cell class
It's better to handle top action right in the cell class. To handle this action on the controller you can closure or delegate like I did.
// Create a delegate protocol
protocol TableViewCellDelegate: AnyObject {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
// add a delegate property
weak var delegate: TableViewCellDelegate?
#IBOutlet var titleTxt: UILabel!
#IBOutlet var likeBtn: UIButton!
//...
override func awakeFromNib() {
super.awakeFromNib()
// You can add target here or an action in the Storyboard/Xib
likeBtn.addTarget(self, action: #selector(likeButtonSelected), for: .touchUpInside)
}
/// Method to update state of the cell
func update(with model: CellViewModel) {
titleTxt.text = model.title
likeBtn.isSelected = model.isLiked
// To use `isSelected` you need to set different images for normal state and for selected state
}
#objc private func likeButtonSelected(_ sender: UIButton) {
sender.isSelected.toggle()
delegate?.didSelectLikeButton(isLiked: sender.isSelected, forCell: self)
}
}
Add an array of models and use it
This is an updated class of ViewController with usage of models.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Provide a list of all models (cells)
private var cellModels: [CellViewModel] = [
CellViewModel(title: "Title 1", isLiked: false),
CellViewModel(title: "Title 2", isLiked: true),
CellViewModel(title: "Title 3", isLiked: false)
]
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return count of cell models
return cellModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let model = cellModels[indexPath.row]
// call a single method to update the cell UI
cell.update(with: model)
// and you need to set delegate in order to handle the like button selection
cell.delegate = self
return cell
}
}
extension ViewController: TableViewCellDelegate {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell) {
// get an indexPath of the cell which call this method
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
// get the model by row
var model = cellModels[indexPath.row]
// save the updated state of the button into the cell model
model.isLiked = isLiked
// and set the model back to the array, since we use struct
cellModels[indexPath.row] = model
}
}
I have a UITableViewCell that contains a bunch of UISwitches. I want the switch to toggle on/off based on what row the user selects, the data is passing to my cell but the switch state is not updating.
I am using a basic MVC, the Storyboard has a TableView > TableViewCell > Label | UISwitch
Controller:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var testList = [1,2,3,4,5]
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.tableFooterView = UIView()
table.delegate = self
table.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return testList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell") as! SwitchCell
//Turn them all on at start
cell.setSwitch(rowSelected: true)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell") as! SwitchCell
cell.setSwitch(rowSelected: false)
}
}
SwitchCell
class SwitchCell: UITableViewCell {
#IBOutlet weak var uiswitch: UISwitch!
#IBOutlet weak var label: UILabel!
func setCell(number: Int){
label.text = String(number)
}
func setSwitch(rowSelected:Bool) {
uiswitch.setOn(rowSelected, animated: true)
}
}
I know I can just make the UISwitch intractable, but I am looking at changing it's state when the user selects the row.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell") as! SwitchCell
cell.setSwitch(rowSelected: false)
}
is incorrect. Using this code you don't pick the cell you want.
You should do simething like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.cellForRow(at: indexPath) as! SwitchCell
cell.setSwitch(rowSelected: false)
}
First of all, we don't dequeue the cell in didSelectRowAt method. Never do that. This will dequeue a brand new cell and won't reflect any changes that you do in it.
So remove the code for tableView(_: didSelectRowAt:) method.
Secondly, you can simply handle the UISwitch state based on cell selection in SwitchCell's definition using setSelected(_:animate:) method like so,
class SwitchCell: UITableViewCell {
#IBOutlet weak var uiswitch: UISwitch!
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
uiswitch.setOn(selected, animated: true)
}
//rest of the code...
}
To avoid bulky code, do all the cell layout in custom cell itself instead of doing it in the delegate or dataSource methods. That will only make you ViewController heavier and non-modular.
I have created prototype custom header cell for a tableView with a button on it. I am trying to get the indexPath of the cell when the button is tapped, but I don't receive it. Any idea what I am doing wrong here?
protocol MediaHeaderCellDelegate: class {
func editPost(cell: MediaHeaderCell)
}
class MediaHeaderCell: UITableViewCell {
weak var delegate: MediaHeaderCellDelegate?
#IBAction func moreOptionsAction(_ sender: UIButton) {
delegate?.editPost(cell: self)
}
}
class NewsfeedTableViewController:UITableViewController, MediaHeaderCellDelegate {
func editPost(cell: MediaHeaderCell) {
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath could not be given")
return}
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.mediaHeaderCell) as! MediaHeaderCell
cell.delegate = self
cell.media = media[section]
return cell
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
cell.delegate = self
return cell
}
}
So this is actually all about learning what section a section header belongs to?? Here’s what I do. I have a header class:
class MyHeaderView : UITableViewHeaderFooterView {
var section = 0
}
I register it:
self.tableView.register(
MyHeaderView.self, forHeaderFooterViewReuseIdentifier: self.headerID)
I use and configure it:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let h = tableView
.dequeueReusableHeaderFooterView(withIdentifier: self.headerID) as! MyHeaderView
// other stuff
h.section = section // *
return h
}
Now if the header view is tappable or contains a button or whatever, learning what section this is the header of is trivial.
Your immediate issue is that you are using a table cell as a section header view. That should not be done. Once you resolve that, your next task is to determine the table section from the header view whose button was tapped.
First, change your MediaHeaderCell to be a header view that extends UITableViewHeaderFooterView and update your protocol accordingly:
protocol MediaHeaderViewDelegate: class {
func editPost(view: MediaHeaderView)
}
class MediaHeaderView: UITableViewHeaderFooterView {
weak var delegate: MediaHeaderViewDelegate?
#IBAction func moreOptionsAction(_ sender: UIButton) {
delegate?.editPost(cell: self)
}
}
Then you need to register the header view in your view controller.
Then update your viewForHeaderInSection:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: Storyboard.mediaHeaderView) as! MediaHeaderView
view.delegate = self
view.media = media[section]
view.tag = section
return view
}
And last, update your protocol method implementation:
func editPost(view: MediaHeaderView) {
let section = view.tag
// do something
}
There is one possible issue with this. If your table allows sections to be added or removed, then it is possible that a header view's tag could be wrong when the button is tapped.
I have 2 files.
myTableViewController.swift
myTableCell.swift
Can I get the indexPath.row in myTabelCell.swift function?
Here is myTableCell.swift
import UIKit
import Parse
import ActiveLabel
class myTableCell : UITableViewCell {
//Button
#IBOutlet weak var commentBtn: UIButton!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var moreBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func likeBtnTapped(_ sender: AnyObject) {
//declare title of button
let title = sender.title(for: UIControlState())
//I want get indexPath.row in here!
}
Here is myTableViewController.swift
class myTableViewController: UITableViewController {
//Default func
override func viewDidLoad() {
super.viewDidLoad()
//automatic row height
tableView.estimatedRowHeight = 450
tableView.rowHeight = UITableViewAutomaticDimension
}
// cell config
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell
}
As you can see... I'm trying to get indexPath.row in myTableCell, liktBtnTapped function.
Could you let me know how can I access or get IndexPath.row?
I have created a UIResponder extension with a recursive method that you can use in any UIView (which inherits from UIResponder) to find a parent view of a specific type.
import UIKit
extension UIResponder {
/**
* Returns the next responder in the responder chain cast to the given type, or
* if nil, recurses the chain until the next responder is nil or castable.
*/
func next<U: UIResponder>(of type: U.Type = U.self) -> U? {
return self.next.flatMap({ $0 as? U ?? $0.next() })
}
}
Using this, we can extend UITableViewCell with some convenient read-only computed properties for the table view and index path of the cell.
extension UITableViewCell {
var tableView: UITableView? {
return self.next(of: UITableView.self)
}
var indexPath: IndexPath? {
return self.tableView?.indexPath(for: self)
}
}
Here is how you could use it in your example:
#IBAction func likeBtnTapped(_ sender: AnyObject) {
//declare title of button
let title = sender.title(for: UIControlState())
//I want get indexPath.row in here!
self.indexPath.flatMap { print($0) }
}
Swift 4+
Try this inside your cell.
func getIndexPath() -> IndexPath? {
guard let superView = self.superview as? UITableView else {
print("superview is not a UITableView - getIndexPath")
return nil
}
indexPath = superView.indexPath(for: self)
return indexPath
}
Easy.. You can do like this inside button action:
let section = 0
let row = sender.tag
let indexPath = IndexPath(row: row, section: section)
let cell: myTableCell = self.feedTableView.cellForRow(at: indexPath) as! myTableCell
And afterwards in cellForRowAtIndexPath:
// add the row as the tag
cell.button.tag = indexPath.row
Another Approach for Swift 4.2 and not assuming Superview will be always a tableview
extension UITableViewCell{
var tableView:UITableView?{
return superview as? UITableView
}
var indexPath:IndexPath?{
return tableView?.indexPath(for: self)
}
}
Usage example
#IBAction func checkBoxAction(_ sender: UIButton) {
guard let indexPath = indexPath else { return }
sender.isSelected = !sender.isSelected
myCustomCellDelegate?.checkBoxTableViewCell(didSelectCheckBox: sender.isSelected, for: indexPath)
}
Swift 4.1. Here I created function to get IndexPath. Just pass your UIView(UIButton,UITextField etc) and UITableView object to get IndexPath.
func getIndexPathFor(view: UIView, tableView: UITableView) -> IndexPath? {
let point = tableView.convert(view.bounds.origin, from: view)
let indexPath = tableView.indexPathForRow(at: point)
return indexPath
}
Create a property indexPath in the cell class and set it in cellForRowAtIndexPath when the cell is reused.
But there is a caveat: Some table view methods to rearrange the cells don't call cellForRowAtIndexPath. You have to consider this case.
But if you use always only reloadData() it's safe and pretty easy.
Another way is to put the code regarding controlling things back in the controller class and run it via callback closures capturing the index path.
Heres another way of doing it
import UIKit
import Parse
import ActiveLabel
class myTableCell : UITableViewCell {
//Button
#IBOutlet weak var commentBtn: UIButton!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var moreBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
class myTableViewController: UITableViewController {
//Default func
//assuming you have an array for your table data source
var arrayOfTitles = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//automatic row height
tableView.estimatedRowHeight = 450
tableView.rowHeight = UITableViewAutomaticDimension
}
// cell config
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell
cell.commentBtn.tag = indexPath.row
cell.commentBtn.addTarget(self, action: #selector(likeBtnTapped(_:), forControlEvents:.TouchUpInside)
//cell config end
#IBAction func likeBtnTapped(sender: UIButton) {
let btn = sender
let indexP = NSIndexPath(forItem: btn.tag, inSection: 0)
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexP) as! myTableCell
//I want get indexPath.row in here!
let title = arrayOfTitles[indexP.row]
//declare title of button
cell.commentBtn.setTitle(title, forState: UIControlState.Normal)
}
}
My solution was subclassing UITableViewCell, so can add IndexPath property. assign custom class for table view cell in storyboard. assign IndexPath value when rowAtIndexPath called.
class MyTableViewCell: UITableViewCell {
var indexPath: IndexPath?
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cellid1", for: indexPath)
(cell as? MyTableViewCell)?.indexPath = indexPath
return cell
}
Swift 5:
if
let collectionView = superview as? UICollectionView,
let index = collectionView.indexPath(for: self)
{
// stuff
}
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