I have some tableviewcells loaded on my tableview. Each of those cells has an arrow button at the top right on the click of which the height of the row is increased and some more buttons are exposed. Clicking on the button again hides the button & the height is decreased as before. The code for that is given like so...
func moreOptionsBtnTapped(cell: CustomersTableViewCell) {
if i == 0 {
if let indexPath = tableView?.indexPath(for: cell) {
selectedIndex = indexPath as NSIndexPath
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.fade)
}
i += 1
} else {
if let indexPath = tableView?.indexPath(for: cell) {
selectedIndex = indexPath as NSIndexPath
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.fade)
}
i = 0
}
}
The heightForRowAtIndexPath is given as:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedIndex as IndexPath {
if i == 0 {
return 92
} else {
return 66
}
}
return 66
}
Now the issue is I also have a search bar on top and on click of that searchbar, if the additional buttons are exposed on the tableview cell then I want them to be minimised. Mere hiding them doesn't work. That has been tried.
Hope somebody can help...
When you are clicking on search bar you will get the call back on UISearchBarDelegate methods like
optional public func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool // return NO to not become first responder
in this method you can change the i value and reload the table view. and in this method you are not getting the indexPath for rows , thats why you use this...
self.tableview.relaodData()
Related
I am doing expand/collapse tableview cells feature in my iOS app. I have multiple sections. And each section has multiple cells. By default, cell height is 100, once user taps on cell, I am increasing height to 200.
So, Based on Bool value, I am changing it. But, While scrolling tableview, It is interchanging the expanded/collapse cells in between sections.
Like if I tap on first section first cell, It is expanding, but after scrolling tableview, Second section first cell also expanding.
My Requirement is, If user tap on particular cell, that cell only should expand/collapse. User can manually expand and close. User can expand multiple cells.
So, I have tried to store Indexpath row and Section.
var expandedIndexSet : IndexSet = []
var expandedIndexSection : IndexSet = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"cellIdentifier", for:
indexPath) as! MyTableViewCell
if expandedIndexSet.contains(indexPath.row) && expandedIndexSection.contains(indexPath.section) { // expanded true
cell.height = 200
//some other data loading here
}
else { //expanded false
cell.height = 100
}
}
#IBAction moreButtonTapped(_ sender: Any) {
if(expandedIndexSet.contains(indexPath.row)) && expandedIndexSection.contains(indexPath.section){
expandedIndexSet.remove(indexPath.row)
expandedIndexSection.remove(indexPath.section)
} else {
expandedIndexSet.insert(indexPath.row)
expandedIndexSection.insert(indexPath.section)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}
Anyone can give better approach than this?
If you store section and row independently in separate arrays, your algorithm will fail.
The reason is that both are dependent:
Think of three expanded cells (row:1, section:1), (row:2, section:1), (row:3, section:2)
Now what happens for the cell (row:3, section:1)?
The row-array contains the value "3", and the section-array contains value "1", therefore it will be considered as expanded.
Therefore, you need to store the index path as a whole - see the sample code:
var expanded:[IndexPath] = []
expanded.append(IndexPath(row:1, section:1))
expanded.append(IndexPath(row:2, section:1))
expanded.append(IndexPath(row:3, section:2))
let checkPath = IndexPath(row:3, section:1)
if (expanded.contains(checkPath)) {
print ("is expanded")
} else {
print ("collapsed")
}
Update
So in your button handle, you'll do the following:
#IBAction moreButtonTapped(_ sender: Any) {
if(expanded.contains(indexPath)) {
expanded.removeAll { (checkPath) -> Bool in
return checkPath == indexPath
}
} else {
expanded.append(indexPath)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}
I'm using a static table view which contains 3 different cells. And when a button in the first cell is tapped, the height of the first cell should increase. Below is the function called when the button is tapped.
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
And in the table view's delegate heightForRowAt()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
let expandedHeight: CGFloat = 415
let collapsedHeight: CGFloat = 115
if self.shouldShowCameraPreview {
return expandedHeight
}
return collapsedHeight
} else if indexPath.row == 2 {
// If already premium, dont show purchases cell.
if AGTUserDefaultValues.isUserPremium {
return 0
}
// Last cell should be same height as the table view
return self.tableView.frame.height - (self.navigationController?.navigationBar.frame.height ?? 0)
- min(UIApplication.shared.statusBarFrame.height, UIApplication.shared.statusBarFrame.width)
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
I've verified that heightForRowAt() IS getting called, and it is working fine for the other cell heights when toggleExpandCamera() is called. It's just that the first cell that is behaving quite weird. It seems like it disappeared or something. I've attached screenshots down below, before and after expanding.
On further inspection, it looks like the cell still exist, but still has the same height. The only difference is there's now more space between the two cells. I also found out the alpha value of the cell is 0.
UPDATE
I've tried created a new project, with only the tableview and the function to expand the cell, and still on that project, the same thing happened. If anyone is curious to help I've uploaded the project here.
I've tried your project and found that changing :
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
into :
self.tableView.reloadData()
Works as expected.
I figured out the answer. In my toggleExpandCamera() function instead of reloading the table view, I replaced that with:
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
And the cell height animates as expected.
Good Morning
I created a custom TableViewCell, which internally has a CollectionViewCell and a directional arrow. I was able to make the cell expand and collapse however I would like it when I expand one cell and I click on another one the previous collapse. Example: If cell is expanded and cell is tapped, I want cell to contract and cell to expand at the same time. So that only one cell can be in its expanded state in any given moment changing the targeting arrow.
Example:
If cellA is expanded and cellB is tapped, I want cellA to contract and cellB to expand at the same time. So that only one cell can be in its expanded state in any given moment changing the targetting arrow.
My code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch(selectedIndexPath)
{
case nil:
let cell = myTableView.cellForRow(at: indexPath) as! TableViewCell
cell.arrowImagem.image = UIImage(named:"content_arrow2")
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath
{
let cell = myTableView.cellForRow(at: indexPath) as! TableViewCell
cell.arrowImagem.image = UIImage(named:"content_arrow")
//cell.reloadInputViews()
selectedIndexPath = nil
}
}
self.myTableView.beginUpdates()
self.myTableView.reloadData()
//myTableView.reloadRows(at: [indexPath], with: .automatic)
self.myTableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let index = indexPath
if selectedIndexPath != nil{
if(index == selectedIndexPath)
{
return 147
}
else{
return 67
}
}
else
{
return 67
}
}
You can do that but you need to do some coding. First of all detect when a row is selected (keep the Index Path of the selected row in a variable). When a row is selected you will add the index path of the new selected row and that of the previously selected (that you have in the variable) in an array that you pass to the tableView reloadRows from inside didSelectRowAt, this function will force redrawing of the two rows and the heightForRowAt function will be called to get the height of both rows, in heightForRowAt you will check the currently selected row (do you remember the variable I mentioned earlier?) and return the expanded height for that, while for the others you return the collapsed height. You should get the result you want. Hope that will help.
You can implement most of the functionality in the didSet handler:
var selectedIndexPath: IndexPath? {
didSet {
guard indexPath != oldValue else {
return
}
if let oldValue = oldValue {
let cell = myTableView.cellForRow(at: indexPath) as? TableViewCell
cell?.arrowImagem.image = UIImage(named:"content_arrow")
}
if let indexPath = indexPath {
let cell = myTableView.cellForRow(at: indexPath) as? TableViewCell
cell?.arrowImagem.image = UIImage(named:"content_arrow2")
}
let rowsToUpdate = [indexPath, oldValue].compactMap { $0 }
self.myTableView.beginUpdates()
// I don't think you actually need to reload rows here but keeping your code here...
self.myTableView.reloadRows(at: rowsToUpdate, with: .automatic)
self.myTableView.endUpdates()
}
}
and then in your didSelect:
if indexPath == selectedIndexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
(written without testing)
I have a tableView, designed in storyboard, that mimics a chat UI. A cell consists of:
A TextView for the message text
A Profile Image of the sender
Right now, the profile image is displayed in every cell, next to the text bubble. This is fine, but if the same users send two or more messages directly after the other, the profile image should only appear on the last bubble and not on the previous one.
I tried calling cellForRowAtIndexPath to get the previous cell's properties and change the hidden property of the profile image, but this gave me two problems:
I'm calling cellForRowAtIndexPath inside cellForRowAtIndexPath, because that's where I make the cell UI and decide wether the profile image has to be hidden or not. I don't think it's a good idea to call this Method inside itself.
Sometimes (when scrolling up and down very fast) this does not work properly.
I also tried to store all the cells in an dictionary (indexPath.row: Cell), so I can access it faster later, but this gave me the same problem namely that it does not work when scrolling up and down really fast.
This is an illustration of how it should be: http://tinypic.com/view.php?pic=2qavj9w&s=8#.Vfcpi7yJfzI
You need to both look ahead inside of your cellForRowAtIndexPath method and, as Paulw11 recommended, call reloadRowsAtIndexPaths after inserting the cell:
import UIKit
struct MyMessage {
let sender: String
let text: String
}
class MyTableViewCell: UITableViewCell {
var message: MyMessage?
var showProfileImage: Bool = false
}
class MyTableViewController: UITableViewController {
private var _messages: [MyMessage] = []
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self._messages.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = self._messages[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyTableViewCell
cell.message = message
if self._messages.count > indexPath.row + 1 {
let nextMessage = self._messages[indexPath.row + 1]
cell.showProfileImage = message.sender != nextMessage.sender
} else {
cell.showProfileImage = true
}
return cell
}
func addMessage(message: MyMessage) {
let lastIndexPath = NSIndexPath(forRow: self._messages.count - 1, inSection: 0)
let indexPath = NSIndexPath(forRow: self._messages.count, inSection: 0)
self._messages.append(message)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
self.tableView.reloadRowsAtIndexPaths([lastIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
}
I have a Image view in my cell which displays a checkmark icon.
What I want to do is when you touch a cell the checkmark should appear. I got this working, but my problem now is that I can't remove the checkmark from the previous selected cell. - only one cell should be able to be selected.
I've tried to get this working in didSelectRowAtIndexPath but I can't get it right so I am stuck right now.
Update:
var selectedRow: NSIndexPath?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: searchCityTableViewCell!
if (indexPath.section == 0) {
cell = tableView.dequeueReusableCellWithIdentifier("staticCityCell") as! searchCityTableViewCell
cell.titleLabel?.text = "Within \(stateName)"
}else if (indexPath.section == 1) {
cell = tableView.dequeueReusableCellWithIdentifier("cityCell") as! searchCityTableViewCell
let state = cities[indexPath.row]
cell.configureWithStates(state)
if indexPath == selectedRow {
cell.cityImage.select()
} else {
cell.cityImage.deselect()
}
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let paths:[NSIndexPath]
if let previous = selectedRow {
paths = [indexPath, previous]
} else {
paths = [indexPath]
}
selectedRow = indexPath
tableView.reloadRowsAtIndexPaths(paths, withRowAnimation: .None)
}
Keep track of which row is currently selected. Add a property to your ViewController:
var selectedRow: NSIndexPath?
In didSelectRowAtIndexPath:
let paths:[NSIndexPath]
if let previous = selectedRow {
paths = [indexPath, previous]
} else {
paths = [indexPath]
}
selectedRow = indexPath
tableView.reloadRowsAtIndexPaths(paths, withRowAnimation: .None)
In cellForRowAtIndexPath:
if indexPath == selectedRow {
// set checkmark image
} else {
// set no image
}
An important thing to note is that the state of which row is selected should be stored in the model and not in the cell. The table should reflect the state of the model. In this case, the model can simply be the indexPath of the selected row. Once the model is updated (selectedRow is set), the affected rows should reload their state.