This is what I currently have
This is the code extending UITableViewDelegate
extension IngredientsViewController: UITableViewDelegate {
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if self.collapses[indexPath.section] == true {
return 0
}
return 50
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 50))
headerView.backgroundColor = UIColor(hexString: AppConfiguration.UIColorCode["orange"]!, alpha: 0.7)
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 12, y: 0, width: tableView.frame.size.width-24, height: 50)) as UILabel
headerString.text = (self.stores[section] as! Store).name!.uppercaseString
headerString.textColor = UIColor.whiteColor()
headerString.font = FontCollection.tableHeaderFont
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if self.deselecting {
self.deselecting = false
return
}
if self.selectedIndexPath.indexOf(indexPath) != nil {
cell.selected = true
}
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedIndexPath.removeAtIndex(self.selectedIndexPath.indexOf(indexPath)!)
self.deselecting = true
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedIndexPath.append(indexPath)
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
let indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
self.collapses[indexPath.section] = !self.collapses[indexPath.section]
//reload specific section animated
let range = NSMakeRange(indexPath.section, 1)
let sectionToReload = NSIndexSet(indexesInRange: range)
self.tableView.reloadSections(sectionToReload, withRowAnimation: .Fade)
}
}
}
My questions
The animation feels like each of the cell is fading in/out by itself. I'd rather have the whole section slide up/down. Been looking around but doesn't find much example of this. And I don't understand why the section header is having the white highlight on touch?
To maintain the selected between collapsing and expanding, I'm storing the selection in an array and use willDisplayCell to determine the selection state upon rendering the cell. Is this the right and most efficient approach?
Using the above code, when a cell is selected, collapse the section that cell belongs to, expand that section, then the said cell become unresponsive to didSelect and didDeselect (while unselected cells are still responsive). Any ideas why? Update for this issue below.
Thank you.
------ UPDATE ------
The above approach I used for rendering selected state with collapsed cell is flawed (issue #3). The below code will make the cell not interactive anymore
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if self.deselecting {
self.deselecting = false
return
}
if self.selectedIndexPath.indexOf(indexPath) != nil {
cell.selected = true
}
}
Instead of that, one should do this
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if self.selectedIndexPath.indexOf(indexPath) != nil {
tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)
}
}
Apple has a great piece of sample code that I believe does exactly what you want: https://developer.apple.com/library/ios/samplecode/TableViewUpdates/Introduction/Intro.html. Summary: you use table header views to insert and remove rows when tapped, and UITableView can animate all that for you.
This StackOverflow answer contains a Swift-ified version of the Apple code, if that's what you'd prefer.
Either way, this approach is significantly more efficient than some implementations I've seen where people hide and show a UIStackView in their rows.
Related
My table view can expand and collapse cells when they are pressed, but the content that appears when the cell expands loads before the animation is finished.
What I am left with is this:
What I would like it to look like is this example. This content appears as if it were behind a curtain and the cell expansion animation just reveals it.
Here is the code that controls the table view:
class HistoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var expandedIndexPath: NSIndexPath? // Index path of the cell that is currently expanded
let collapsedHeight: CGFloat = 44.0 // Constant to set the default collapsed height
var ticketHistoryService = TicketHistoryService() // Service to gather info about Ticket History CoreData
var tickets = [Ticket]()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Remove any appended table view cells
tableView.tableFooterView = UIView()
self.tickets = self.ticketHistoryService.fetchData() // Load inital data
}
// MARK: - Table View Methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tickets.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! HistoryTableViewCell
let ticket = self.tickets[indexPath.row]
cell.titleLabel!.text = ticket.ticketNumber
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
self.ticketHistoryService.removeObject(indexPath.row)
self.tickets = self.ticketHistoryService.fetchData()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.beginUpdates()
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! HistoryTableViewCell
if indexPath.isEqual(self.expandedIndexPath){ // If currently selected cell was just previously selected
self.expandedIndexPath = nil
cell.commentLabel.hidden = true
}
else {
self.expandedIndexPath = indexPath
cell.commentLabel.hidden = false
}
self.tableView.endUpdates()
}
func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! HistoryTableViewCell
cell.commentLabel.hidden = true
return indexPath
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.isEqual(self.expandedIndexPath) {
return UITableViewAutomaticDimension
}
return collapsedHeight
}
}
One approach is to have your cell clip subview content that would expand outside of itself:
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! HistoryTableViewCell
cell.clipsToBounds = true
In my app i have a tableview with a custom cell which contains a collectionview and a button in it.I want to implement expanded functionality in my app.The issue is that when the expand a paticular cell, the collectionview data and button are not being clicked.I found out that when i fixed the table height, my collectionview and buttons are getting clicked but when i calculate the expanded height and assign it to tableview at that time its not being clicked.
Code
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! YourBudgetTableViewCell
cell.budgetIcon.image = UIImage(named: "ic_your_budget_close");
// cell.collectionView.tag = indexPath.row;
// cell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
cell.selected=true;
isCellSelected = indexPath.row;
self.tableView.beginUpdates();
self.tableView.endUpdates();
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! YourBudgetTableViewCell
cell.budgetIcon.image = UIImage(named: "ic_your_budget_expand");
isCellSelected = -1;
self.tableView.beginUpdates();
self.tableView.endUpdates();
}
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath)!
if selectedCell.selected {
self.tableView.deselectRowAtIndexPath(indexPath, animated: false);
self.tableView(self.tableView, didDeselectRowAtIndexPath: indexPath)
return nil;
}
return indexPath
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if isCellSelected == indexPath.row
{
return 200;
}
else
{
return 44;
}
// return 200;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listBudget.count
}
So if i give fixed height say 200 in heightForRowAtIndexPath the click works fine, but if not given fixed click is not working
Check for the height for the expended cell or set alternate background color to cell. You will see that cell does not have that height which is required to display CollectionView. Because of that you are not able to click on the buttons at bottom of cell.
I'm creating an iOS app using swift. I want to build a non scrollable tableView which shows on screen all informations contained in datasource, so the height of each cell depends on the number of entries in data. For example, if the height of the view is 500 and data.count = 10, each cell's height is 50. A problem appears when the cell's height is ~100.8 (corresponding to 5 entries in my data, using my iPhone 5). In fact, even by setting tableView.separatorStyle = .None , a weird separator appears for this cell's height.
Below, the first image (7 entries in data) is normal and on the second (5 entries in data) those separators appear.
Here is my view controller :
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let reuseIdentifier = "Cell"
var data = ["bobby", "bob", "john", "helena", "clara", "oliver", "steve"]
var visibleHeight:CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = .None
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
editModeOff()
tableView.scrollEnabled = false
visibleHeight = viewVisibleSize.height
tableView.separatorStyle = .None
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = data[indexPath.row]
cell.selectionStyle = .None
return cell
}
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let stringToMove = data[sourceIndexPath.row]
data.removeAtIndex(sourceIndexPath.row)
data.insert(stringToMove, atIndex: destinationIndexPath.row)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete{
data.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
print(visibleHeight/CGFloat(data.count))
return visibleHeight/CGFloat(data.count)
}
func editModeOn(){
tableView.setEditing(true, animated: true)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "editModeOff")
}
func editModeOff(){
tableView.setEditing(false, animated: true)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: "editModeOn")
}
}
extension ViewController{
var viewVisibleSize:CGSize{
var size = view.bounds.size
if !UIApplication.sharedApplication().statusBarHidden{
size.height -= UIApplication.sharedApplication().statusBarFrame.height
}
if let navigationController = navigationController{
size.height -= navigationController.navigationBar.bounds.height
}
if let tabBarController = tabBarController{
size.height -= tabBarController.tabBar.bounds.height
}
return size
}
}
I always clear the color of the separator:
tableView.separatorColor = UIColor.clearColor()
I have found a dirty solution by setting :
cell.contentView.layer.borderWidth = 0.5
cell.contentView.layer.borderColor = UIColor.whiteColor().CGColor
in tableView:cellForRowAtIndexPath: method. But for sure, there is a better way...
In your viewDidLoad: you can try self.tableView.tableFooterView = UIView()
It is funny. Problem in your function tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
It has strange behavior for some float numbers and a separator will appear.
I offer you round a returning height like this:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
print(self.visibleHeight/CGFloat(self.tableArray.count))
let rett = self.visibleHeight/CGFloat(self.tableArray.count)
let convert = NSString(format: "%.0f", rett)
return CGFloat(convert.floatValue)
}
I have the following swift code to implement a UITableViewRowAction, when I swipe a row the row slides out to the left as expected, however all the Section Header Rows also slide to the left with the table row at the same time.
I have also included a screen shot to show what is happening
If I remove the viewForHeader override and replace it with titleForHeaderInSection then I have no problem.
The reason for overiding the viewForHeader is that I want to place an image in the Header Row.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCellWithIdentifier("header") as UITableViewCell
cell.textLabel?.text = instrumentGroups[section].name
cell.imageView?.image = UIImage(named: instrumentGroups[section].name)
return cell
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> InstrumentTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("instrumentCell", forIndexPath: indexPath) as InstrumentTableViewCell
cell.instrument = instrumentGroups[indexPath.section].instruments[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
let instrument = instrumentGroups[indexPath.section].instruments[indexPath.row]
var actions: [AnyObject] = []
var action = UITableViewRowAction(style: .Normal, title: "Remove Watch") { (action, indexPath) -> Void in
tableView.editing = false
instrument.SetWatchList(false)
self.refresh()
}
action.backgroundColor = UIColor.redColor()
actions.append(action)
return actions
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
}
Simply return cell.contentView instead of cell within your viewForHeaderInSection function and your problem will be resolved.
I had the same problem. The answer is simple.
Because you use UITableViewCell as header view. Try UILabel or UITableViewHeaderFooterView instead.
I have a UITableView, and added it as a subview of a UIView.
Whenever I select a cell that had everything disappears in UITableView.
Already had this problem at other times, however it stopped happening without changes on code.
Currently I use Swift, however already happened in Objective-C.
Code:
TableView delegates:
numberOfSectionsInTableView
numberOfRowsInSection
cellForRowAtIndexPath
heightForRowAtIndexPath
didSelectRowAtIndexPath
Also registered the identifier of the cell
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
listObjectsToList = ["String 1","String 2","String 3"]
self.view.frame.size = CGSizeMake(300, 110)
self.view.layer.cornerRadius = 5
self.view.layer.masksToBounds = true
self.tableView.scrollEnabled = false
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listObjectsToList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
cell.textLabel.text = listObjectsToList.objectAtIndex(indexPath.row) as? String
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 40
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
let objectSelected: AnyObject = listObjectsToList.objectAtIndex(indexPath.row)
object.type = objectSelected as String
self.view.removeFromSuperview()
}
However the method didSelectRowAtIndexPath is not called...
Code adding tableview:
var listTypeTableViewController = ListTypeTableViewController()
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
self.view.addSubview(listTypeTableViewController.view)
I have tried:
var listTypeTableViewController = ListTypeTableViewController()
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
self.view.addSubview(listTypeTableViewController.tableView)
But without success
The problem was and I was just trying to extract the UITableView to show it. When I need to add a UITableViewController as a child of my UIViewController:
var listTypeTableViewController = ListTypeTableViewController(nibName: "TableViewTypeScheduling", bundle: nil)
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
addChildViewController(listTypeTableViewController)
view.addSubview(listTypeTableViewController.view)
The reason is this line in tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) method:
self.view.removeFromSuperview()
You remove a view and all of its superviews, including the table view.