I have a static table in a TableViewController. Whenever I select a row, it fills the entire cell with a grey color. It does this for every row I tap. If I use:
cell.selectionStyle = .none
It will make the cell fill with white instead.
Tableview Attributes Inspector:
TableViewCell Attributes Inspector:
TableViewController:
import UIKit
class OptionTableViewController: UITableViewController {
#IBOutlet var optionsTable: UITableView!
let defaults = UserDefaults.standard
let numberOfRows = [7,2]
let cellIdentifier = "OptionCells"
override func viewDidLoad() {
super.viewDidLoad()
optionsTable.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that con be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
var rows = 0
if(section < numberOfRows.count){
rows = numberOfRows[section]
}
return rows
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.selectionStyle = .none
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.text = optionSelections[indexPath.row]
return cell
}
}
I believe I have everything setup correctly. Why does it fill in the cells when I tap them?
Update:
I updated the code to show what I currently have.
Update 2:
I've included a couple of pictures to show what the table looks like before and after tapping every other cell.
Update 3:
I was dequeuing cells to change the accessory type to checkmark.
Before:
After:
As discussed in comments, I think the problem is a symptom of dequeuing cells in the didSelectRow(at:) method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
}
You should instead use
let cell = tableView.cellForRow(at: indexPath)
That gets the cell that is currently at the given indexPath (be aware that it may return nil if that indexPath has been scrolled off or was never on screen). Dequeuing gets an unused cell to go at the given indexPath - which (I think) then sits in front of the existing cell - hence the weird behaviour.
You need to set selectionStyle BEFORE the selection happens.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.selectionStyle = .none
cell.selectedBackgroundView = UIView() // optional
cell.selectedBackgroundView?.backgroundColor = UIColor.white // optional
}
Related
I take tableView and two different cell.xib files , I want to display when i click cell1 then i should display cell2 data.
class TableView: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var array1 = ["Click1","Click2"]
var array2 = [[ "one","two","Three"],["Four","Five"]]
var selectedArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "MainCell", bundle: nil) , forCellReuseIdentifier: "MainCell")//This is used to add xib file with identifier
tableView.register(UINib(nibName: "SecondCell", bundle: nil) , forCellReuseIdentifier: "SecondCell")
}
//MARK:DataSource Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
cell.textLabel?.text = array1[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
selectedArray = array2[indexPath.row]
cell.textLabel?.text = selectedArray[indexPath.row]
}
}
Tell how can i do that if i press first cell it should show 2nd cell values as per indexPath.row
I think you want to achieve expandable cells. You can use the header cell for this one.
You might want to read this:
Hope this helps!
For example, you can add a flag which indicates whether the first cell was tapped.
var wasFirstCellTapped = false
Then numberOfRowsInSection depends on this flag:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wasFirstCellTapped ? array1.count : 1
}
In didSelectRowAt:indexPath set this flag to true. And perform tableView changes. The simplest way is to call tableView.reloadData(). But you can animate this using insertRowsAtIndexPaths
var allCellsArray = ["Click1","Click2"]
var displayingCellsArray = ["Click1"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayingCellsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if displayingCellsArray[indexPath.row] == "Click1" {
cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
}else {
cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
}
cell.textLabel?.text = displayingCellsArray[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = displayingCellsArray[indexPath.row]
//Below logic will show (Cell2 if Cell1 is clicked and hide Cell1)
// (and show Cell1 if clicked Cell2 and hide Cell2)
if data == "Click1" {
displayingCellsArray = [allCellsArray.last!]
}else {
displayingCellsArray = [allCellsArray.first!]
}
tableView.reloadData()
}
You should handle such logic in a separate datasource array by adding/removing that sort of data from this array which shows particular cells.
E.g
In your case you have two values in array Click1 & Click2
Click1 shows MainCell & Click2 shows SecondCell
So first add Click1 in your array and when this cell is tapped simply add Click2 in your array and reload. If you want to remove MainCell when SecondCell is displaying then while adding Click2 simply remove Click1
You can expand the second cell on click of first cell..
Refer this example to expand and collapse the cells
https://github.com/jonasman/JNExpandableTableView
I have a UITableViewController with a custom UITableViewCell. Each cell has 2 labels. When the cell is selected it expands to a fixed value, I do it via tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat . I also have set the rowHeight = UITableViewAutomaticDimension , as some cells have to display multiple lines of text.
What I want to achieve is when a cell needs to be expanded I want to add 50 points to its current height. So here's the question, how can I get current height of the cell, when the rowHeight = UITableViewAutomaticDimension is set?
Here's my code for the fixed height for the selected state:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return selectedHeight
}else{
return tableView.estimatedRowHeight
}
}
EDIT: I also need to change it after that, by adding some variable to it.
Building on HamzaLH's answer you might do something like this...
import UIKit
class TableViewController: UITableViewController {
var selectedRow: Int = 999 {
didSet {
tableView.beginUpdates()
tableView.endUpdates()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRow { //assign the selected row when touched
let thisCell = tableView.cellForRow(at: indexPath)
if let thisHeight = thisCell?.bounds.height {
return thisHeight + 50
}
}
return 60 //return a default value in case the cell height is not available
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedRow = indexPath.row
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.detailTextLabel?.text = "test"
return cell
}
}
I'm animating the expansion of the height with a didSet when a the selectedRow is altered.
Also, don't forget you may still need to connect your dataSource and delegate by dragging the outlets in Interface Builder to your View Controller in the storyboard. After that you still need to add this to ViewDidLoad in your ViewController's swift file.
tableView.delegate = self
tableView.dataSource = self
I also have an Outlet declared for the tableView like below, and connected in Interface Builder storyboard.
#IBOutlet weak var tableView: UITableView!
You need to get the cell using cellForRow and then get the height of the cell.
let cell = tableView.cellForRow(at: indexPath)
let height = cell.bounds.height
I created a table view with a custom prototype cell but I need to shape the borders, this is the image I have now
the one i get when i run the app
this is the prototype cell
please note that I created a new class for the tableviewcell where I added a textfield to be changed from a list
I want to set the corners radius
i tried to add this code,
layer.cornerRadius = 10
and
layer.masksToBounds = true
in the user defined runtime attributes like I did before for a button but it doesn't work
Here is the code:
import UIKit
class ListOffersViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let profil = ["Eric","Eric","Eric","Eric","Eric","Eric"]
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profil.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ListOffersViewControllerTableViewCell
cell.profilName.text = profil[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Be attentive when you work with UITableViewCell. UI features you mind make with contentView.
Here is example.
class ViewController: UITableViewController {
let identifier = "roundedCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = "\(indexPath)"
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = .red
cell.contentView.backgroundColor = .blue
cell.contentView.layer.cornerRadius = 10.0
}
}
Try adding a UIView on the cell and set the constraints so that it is the same size as the cell. Put all your design objects inside that view. Connect that view to your cell file, then add the corner radius to that view. Make sure you set the cell view background to transparent, and add colour to the new UIView.
I am making a music genre picking application and when I go to my table to select genres, I select a row and it selects a random row about 10 or so down from my selection.
My code for the selection is:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let genresFromLibrary = genrequery.collections
let rowitem = genresFromLibrary![indexPath.row].representativeItem
print(rowitem?.value(forProperty: MPMediaItemPropertyGenre) as! String
)
if let cell = tableView.cellForRow(at: indexPath)
{
cell.accessoryType = .checkmark
}
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath)
{
cell.accessoryType = .none
}
}
Cells are reused by default when cellForRowAtIndexPath is called. This causes the cells to have the wrong data when you don't keep track of the indexPaths that have been selected. You need to keep track of the index paths that are currently selected so you can show the appropriate accessory type in your table view.
One way of doing it is to have a property in your UITableViewController that just stores the index paths of the selected cells. It can be an array or a set.
var selectedIndexPaths = Set<IndexPath>()
When you select a row on didSelectRowAt, add or remove the cell from selectedIndexPaths, depending on whether the index path is already in the array or not:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndexPaths.contains(indexPath) {
// The index path is already in the array, so remove it.
selectedIndexPaths.remove(indexPathIndex)
} else {
// The index path is not part of the array
selectedIndexPaths.append(indexPath)
}
// Show the changes in the selected cell (otherwise you wouldn't see the checkmark or lack thereof until cellForRowAt got called again for this cell).
tableView.reloadRows(at: [indexPath], with: .none)
}
Once you have this, on your cellForRowAtIndexPath, check if the indexPath is in the selectedIndexPaths array to choose the accessoryType.
if selectedIndexPaths.contains(indexPath) {
// Cell is selected
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
This should solve the problem of the seemingly random cells that are checked every 10 cells down or so (which, is not random, it's just that the cell with the checkmark is being reused).
Because cellForRow returns a cached cell you generated. When scrolling out of the screen the order of cells are changed and cells are reused. So it seems "randomly selected".
Don use cellForRow, instead record selection data.
Here's code works in a single view playground.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
var selection: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = self.view.bounds
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "c")
if let sc = cell {
sc.accessoryType = .none
let isSelected = selection[indexPath] ?? false
sc.accessoryType = isSelected ? .checkmark : .none
return sc
}
return UITableViewCell(style: .default, reuseIdentifier: "c")
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.textLabel?.text = NSNumber(value: indexPath.row).stringValue
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selection[indexPath] = true
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
I have already asked this doubt/problem in SO. but not get get solution. Please help me out....
i have one table view which will show the list of name data till 10 datas. But what i need is , when user press any cell, that cell should be replace with another cell, which have some image, phone number, same data name. How to do that.
I have two xib : 1. normalcell, 2. expandable/replace cell
Here is my viewconrolelr.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var Resultcount: UILabel!
var tableData = ["thomas", "Alva", "Edition", "sath", "mallko", "techno park",... till 10 data]
let cellSpacingHeight: CGFloat = 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var nib = UINib(nibName:"customCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
Resultcount.text = "\(tableData.count) Results"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.tableData.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
// Make the background color show through
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clearColor()
return headerView
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:customCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
cell.vendorName.text = tableData[indexPath.section]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Starting my cell will look like this :
When i press that cell, i need some thing to do like this with replace ment of like below cell :
But when i press same cell again, again it should go to normal cell.
How to do that ??
First modify your tableView:cellForRowAtIndexPath: implementation as follows. Then you need to implement the click handler. One way would be in the MyCell class. Another would be to override selectRowAtIndexPath. Without knowing more about what you want (e.g. multiple vs single selection), it's hard to give actual code but here's something.
BOOL clickedRows[MAX_ROWS]; // Init this array as all false in your init method. It would be better to use NSMutableArray or something similar...
// selectRowAtIndexPath code
int row = indexPath.row
if(clickedRows[row]) clickedRows[row]=NO; // we reverse the selection for the row
else clickedRows[row]=YES;
[self.tableView reloadData];
// cellForRowAt... code
MyCell *cell = [tableView dequeueResuableCell...
if(cell.clicked) { // Nice Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
} else { // Grey Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
}
You need to create two independent cell on xib. Then you can load using check.You can copy and paste it will work perfectly.
in cellForRowAt like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if selectedIndexPath == indexPath && self.isExpand == true{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceExpandedCell", for: indexPath) as! LeaveBalanceExpandedCell
cell.delegate = self
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceNormalCell", for: indexPath) as! LeaveBalanceNormalCell
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// cell.animateCell(cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndexPath == indexPath{
if isExpand == true{
self.isExpand = false
}
else{
self.isExpand = true
}
}
else{
selectedIndexPath = indexPath
self.isExpand = true
}
self.tableView.reloadData()
}