How to toggle open close uitableViewCell [duplicate] - ios

I'm able to expand and collapse cells but i wanna call functions (expand and collapse) inside UITableViewCell to change button title.
import UIKit
class MyTicketsTableViewController: UITableViewController {
var selectedIndexPath: NSIndexPath?
var extraHeight: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTicketsTableViewCell
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(selectedIndexPath != nil && indexPath.compare(selectedIndexPath!) == NSComparisonResult.OrderedSame) {
return 230 + extraHeight
}
return 230.0
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
}
import UIKit
class MyTicketsTableViewCell: UITableViewCell {
#IBOutlet weak var expandButton: ExpandButton!
#IBOutlet weak var detailsHeightConstraint: NSLayoutConstraint!
var defaultHeight: CGFloat!
override func awakeFromNib() {
super.awakeFromNib()
defaultHeight = detailsHeightConstraint.constant
expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
detailsHeightConstraint.constant = 30
}
func expand() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.99))
self.detailsHeightConstraint.constant = self.defaultHeight
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("CLOSE", forState: .Normal)
})
}
func collapse() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.0))
self.detailsHeightConstraint.constant = CGFloat(30.0)
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
})
}
}

If you want the cell to get physically bigger, then where you have your store IndexPath, in heightForRow: use:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return 230 + extraHeight
}
return 230.0
}
Then when you want to expand one in the didSelectRow:
selectedIndexPath = indexPath
tableView.beginUpdates
tableView.endUpdates
Edit
This will make the cells animate themselves getting bigger, you dont need the extra animation blocks in the cell.
Edit 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.collapse()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.collapse()
}
} else {
selectedIndexPath = indexPath
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.expand()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.expand()
}
}
tableView.beginUpdates()
tableView.endUpdates()
}

All you need is implement UITableView Delegate this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
By default, estimatedHeight is CGRectZero, when you set some value for it, it enables autoresizing (the same situation with UICollectionView), you can do even also so:
tableView?.estimatedRowHeight = CGSizeMake(50.f, 50.f);
Then you need to setup you constraints inside your cell.
Check this post: https://www.hackingwithswift.com/read/32/2/automatically-resizing-uitableviewcells-with-dynamic-type-and-ns
Hope it helps)

In MyTicketsTableViewController class, inside cellForRowAtIndexPath datasource method add target for the button.
cell.expandButton.addTarget(self, action: "expandorcollapsed:", forControlEvents: UIControlEvents.TouchUpInside)

I tried to implement lots of the given examples on this and other pages with similar questions, but none worked for me since I had to perform some logic in my custom cell (eg. hide unneeded UILables in CustomCell.swift when the cell is collapsed).
Here is the implementation that worked for me:
Create a dictionary:
private var _expandedCells: [IndexPath:Bool] = [:]
Implement the heightForRowAt method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return _expandedCells[indexPath] == nil ? 70 : _expandedCells[indexPath]! ? 150 : 70
}
Implement the didSelectRowAt method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
_expandedCells[indexPath] = _expandedCells[indexPath] == nil ? true : !_expandedCells[indexPath]!
tableView.reloadRows(at: [indexPath], with: .fade)
}
Adjust your customCell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YourCustomTableViewCell, let isExpanded = _expandedCells[indexPath] else { return }
cell.set(expanded: isExpanded)
}

Related

How can I call a tableview delegate methods from one class to another class in swift?

I want to perform a drop down for each cell hence I have inserted a tableview with in a cell and I am working with the expanding cell that a cell gets expanded when it clicks, so that it will show as a dropdown.
MainTableView:
import UIKit
class MainTableView: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var mainTableView: UITableView!
var selectedCellIndexPath: NSIndexPath?
let selectedCellHeight: CGFloat = 300.0
let unselectedCellHeight: CGFloat = 44.0
var name = ["red","green","blue","white"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = mainTableView.dequeueReusableCellWithIdentifier("cell1" , forIndexPath: indexPath) as! mainTableViewCell
cell.Title.text = name[indexPath.row]
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
}
return unselectedCellHeight
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedCellIndexPath != nil && selectedCellIndexPath == indexPath {
selectedCellIndexPath = nil
} else {
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
if selectedCellIndexPath != nil {
// This ensures, that the cell is fully visible once expanded
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .None, animated: true)
}
}
}
MainTableViewCell:
import UIKit
class MainTableViewCell:UITableViewCell,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var dropDownList: UITableView!
#IBOutlet weak var Title: UILabel!
var selectedCellIndexPath: NSIndexPath?
let selectedCellHeight: CGFloat = 0.0
let unselectedCellHeight: CGFloat = 44.0
var name = ["magenta","yellow","orange","cyan","black","grey"]
override func awakeFromNib() {
super.awakeFromNib()
dropDownList.delegate = self
dropDownList.dataSource = self
}
override func setSelected(selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = dropDownList.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath) as! subTableViewCell
cell.subTitle.text = name[indexPath.row]
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
}
return unselectedCellHeight
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
/* If I select any row in subTableView then the tableview has to disappear and the height of mainTableView row should be rearranged(i.e the dropdown should dissappear) */
}
InnerTableViewCell:
import UIKit
class subTableViewCell: UITableViewCell {
#IBOutlet weak var subTitle: 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
}
}
If I call the mainTableView delegate methods in the mainTableViewCell class then it would be easy but I am getting struggle with this .Help me!
First Create a delegate method to cell :-
import UIKit
protocol MainTableViewCellDelegate {
func myFunc()
}
class MainTableViewCell:UITableViewCell,UITableViewDataSource,UITableViewDelegate {
var MainTableViewCellDelegate! = nil
// Put Your Other code Lines
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate.myFunc()
}
}
Then in your MainTableView add MainTableViewCellDelegate :-
import UIKit
class MainTableView: UIViewController,UITableViewDelegate,UITableViewDataSource, MainTableViewCellDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = dropDownList.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath) as! subTableViewCell
cell.subTitle.text = name[indexPath.row]
cell.delegate = self
return cell
}
func myFunc() {
self.performSegueWithIdentifier("yourSegueIdentifier", sender: nil) //Put your segue
}
}

After reordering cell is behind another cell

I'm trying to create a reorder indicator. So what I'm trying to do is that when the user is dragging the cell. That on the place the user wants to drop the cell an other cell is added with a background. I already achieved that. But when the user drops the cell, the indicator cell needs to be deleted and the table must stay the same like the user intended to.
Now I have the following problem. When the user drops the cell. The indicator cell is correctly removed but the dropped cell is behind another cell suddenly.
Screenshots to clarify:
My code:
import UIKit
import PureLayout
class ViewController: UIViewController {
lazy var tableView: UITableView = {
let tableView = UITableView(forAutoLayout: ())
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .SingleLine
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Default")
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Indicator")
tableView.tableFooterView = UIView()
return tableView
}()
private var data: [Int] = [Int]()
private var isIndicatorRowAddedInSection: Bool = false
private var lastIndicatorRowIndexPath: NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.tableView)
self.tableView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsZero, excludingEdge: .Top)
self.tableView.autoPinEdgeToSuperviewEdge(.Top, withInset: 40)
for index in 0..<20 {
data.append(index)
}
self.tableView.editing = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: UITableViewDelegate {
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 44
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
if let lastIndexPath = self.lastIndicatorRowIndexPath {
print("Need to delete a row on section \(lastIndexPath.section) row \(lastIndexPath.row)")
self.isIndicatorRowAddedInSection = false
self.lastIndicatorRowIndexPath = nil
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([lastIndexPath], withRowAnimation: .Fade)
tableView.endUpdates()
}
let itemToMove = self.data[fromIndexPath.row]
self.data.removeAtIndex(fromIndexPath.row)
self.data.insert(itemToMove, atIndex: toIndexPath.row)
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return .None
}
func tableView(tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
func tableView(tableView: UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath,
toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath {
if let lastIndexPath = self.lastIndicatorRowIndexPath {
print("Target indexpath indexpath to delete row \(lastIndexPath.section) row \(lastIndexPath.row)")
self.isIndicatorRowAddedInSection = false
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([lastIndexPath], withRowAnimation: .Fade)
tableView.endUpdates()
self.lastIndicatorRowIndexPath = nil
}
self.isIndicatorRowAddedInSection = true
self.lastIndicatorRowIndexPath = proposedDestinationIndexPath
tableView.beginUpdates()
print("Indicator view inserted at section \(proposedDestinationIndexPath.section) row \(proposedDestinationIndexPath.row)")
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: proposedDestinationIndexPath.row, inSection: proposedDestinationIndexPath.section)], withRowAnimation: .Automatic)
tableView.endUpdates()
return proposedDestinationIndexPath
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.isIndicatorRowAddedInSection {
return self.data.count + 1
}
return self.data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let indicatorIndexPath = self.lastIndicatorRowIndexPath where isIndicatorRowAddedInSection {
if indicatorIndexPath.section == indexPath.section && indicatorIndexPath.row == indexPath.row {
let cell = tableView.dequeueReusableCellWithIdentifier("Indicator", forIndexPath: indexPath)
return cell
}
}
let cell = tableView.dequeueReusableCellWithIdentifier("Default", forIndexPath: indexPath)
cell.textLabel?.text = String(self.data[indexPath.row])
return cell
}
}
I don't see the problem. Can someone help me?
I found the problem. In the moveRow it is executed on Thread 1 (main thread) but it seems like it's not. Because when you wrap it with a dispatch async to the main thread it just works.
Full working code:
import UIKit
import PureLayout
class ViewController: UIViewController {
lazy var tableView: UITableView = {
let tableView = UITableView(forAutoLayout: ())
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .SingleLine
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Default")
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Indicator")
tableView.tableFooterView = UIView()
return tableView
}()
private var data: [Int] = [Int]()
private var isIndicatorRowAddedInSection: Bool = false
private var lastIndicatorRowIndexPath: NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.tableView)
self.tableView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsZero, excludingEdge: .Top)
self.tableView.autoPinEdgeToSuperviewEdge(.Top, withInset: 40)
for index in 0..<5 {
data.append(index)
}
self.tableView.editing = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: UITableViewDelegate {
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 44
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
let itemToMove = self.data[fromIndexPath.row]
self.data.removeAtIndex(fromIndexPath.row)
self.data.insert(itemToMove, atIndex: toIndexPath.row - 1)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let lastIndexPath = self.lastIndicatorRowIndexPath {
print("Need to delete a row on section \(lastIndexPath.section) row \(lastIndexPath.row)")
self.isIndicatorRowAddedInSection = false
self.lastIndicatorRowIndexPath = nil
tableView.beginUpdates()
// Need to put some logic if you pull to top it has to be + 1
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: lastIndexPath.row - 1, inSection: 0)], withRowAnimation: .Fade)
tableView.endUpdates()
}
})
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return .None
}
func tableView(tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
func tableView(tableView: UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath,
toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath {
if let lastIndexPath = self.lastIndicatorRowIndexPath {
print("Target indexpath indexpath to delete row \(lastIndexPath.section) row \(lastIndexPath.row)")
self.isIndicatorRowAddedInSection = false
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([lastIndexPath], withRowAnimation: .None)
tableView.endUpdates()
self.lastIndicatorRowIndexPath = nil
}
self.isIndicatorRowAddedInSection = true
self.lastIndicatorRowIndexPath = proposedDestinationIndexPath
tableView.beginUpdates()
print("Indicator view inserted at section \(proposedDestinationIndexPath.section) row \(proposedDestinationIndexPath.row)")
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: proposedDestinationIndexPath.row, inSection: proposedDestinationIndexPath.section)], withRowAnimation: .Automatic)
tableView.endUpdates()
return proposedDestinationIndexPath
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.isIndicatorRowAddedInSection {
return self.data.count + 1
}
return self.data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let indicatorIndexPath = self.lastIndicatorRowIndexPath where isIndicatorRowAddedInSection {
if indicatorIndexPath.section == indexPath.section && indicatorIndexPath.row == indexPath.row {
let cell = tableView.dequeueReusableCellWithIdentifier("Indicator", forIndexPath: indexPath)
cell.textLabel?.text = "IndicatorView: ROW \(indexPath.row) SECTION = \(indexPath.section)"
print("Indictorview cell for row")
return cell
}
}
let cell = tableView.dequeueReusableCellWithIdentifier("Default", forIndexPath: indexPath)
cell.textLabel?.text = "ROW = \(indexPath.row) SECTION = \(indexPath.section)"
return cell
}
}

Expand and Collapse tableview cells

I'm able to expand and collapse cells but i wanna call functions (expand and collapse) inside UITableViewCell to change button title.
import UIKit
class MyTicketsTableViewController: UITableViewController {
var selectedIndexPath: NSIndexPath?
var extraHeight: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTicketsTableViewCell
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(selectedIndexPath != nil && indexPath.compare(selectedIndexPath!) == NSComparisonResult.OrderedSame) {
return 230 + extraHeight
}
return 230.0
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
}
import UIKit
class MyTicketsTableViewCell: UITableViewCell {
#IBOutlet weak var expandButton: ExpandButton!
#IBOutlet weak var detailsHeightConstraint: NSLayoutConstraint!
var defaultHeight: CGFloat!
override func awakeFromNib() {
super.awakeFromNib()
defaultHeight = detailsHeightConstraint.constant
expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
detailsHeightConstraint.constant = 30
}
func expand() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.99))
self.detailsHeightConstraint.constant = self.defaultHeight
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("CLOSE", forState: .Normal)
})
}
func collapse() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.0))
self.detailsHeightConstraint.constant = CGFloat(30.0)
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
})
}
}
If you want the cell to get physically bigger, then where you have your store IndexPath, in heightForRow: use:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return 230 + extraHeight
}
return 230.0
}
Then when you want to expand one in the didSelectRow:
selectedIndexPath = indexPath
tableView.beginUpdates
tableView.endUpdates
Edit
This will make the cells animate themselves getting bigger, you dont need the extra animation blocks in the cell.
Edit 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.collapse()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.collapse()
}
} else {
selectedIndexPath = indexPath
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.expand()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.expand()
}
}
tableView.beginUpdates()
tableView.endUpdates()
}
All you need is implement UITableView Delegate this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
By default, estimatedHeight is CGRectZero, when you set some value for it, it enables autoresizing (the same situation with UICollectionView), you can do even also so:
tableView?.estimatedRowHeight = CGSizeMake(50.f, 50.f);
Then you need to setup you constraints inside your cell.
Check this post: https://www.hackingwithswift.com/read/32/2/automatically-resizing-uitableviewcells-with-dynamic-type-and-ns
Hope it helps)
In MyTicketsTableViewController class, inside cellForRowAtIndexPath datasource method add target for the button.
cell.expandButton.addTarget(self, action: "expandorcollapsed:", forControlEvents: UIControlEvents.TouchUpInside)
I tried to implement lots of the given examples on this and other pages with similar questions, but none worked for me since I had to perform some logic in my custom cell (eg. hide unneeded UILables in CustomCell.swift when the cell is collapsed).
Here is the implementation that worked for me:
Create a dictionary:
private var _expandedCells: [IndexPath:Bool] = [:]
Implement the heightForRowAt method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return _expandedCells[indexPath] == nil ? 70 : _expandedCells[indexPath]! ? 150 : 70
}
Implement the didSelectRowAt method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
_expandedCells[indexPath] = _expandedCells[indexPath] == nil ? true : !_expandedCells[indexPath]!
tableView.reloadRows(at: [indexPath], with: .fade)
}
Adjust your customCell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YourCustomTableViewCell, let isExpanded = _expandedCells[indexPath] else { return }
cell.set(expanded: isExpanded)
}

How to expand and collapse cell correctly like accordion in Swift?

In my app I am trying to play audio files in UITableView. When I tap the cell, it has to expand and play audio. Then if I tap another cell, it should act like accordion, the previous cell must collapse and audio to be stopped. But my implementation doesn't do this:
import UIKit
import AVFoundation
class AudioTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var audioPlayer: AVAudioPlayer?
let mp3sPaths = NSBundle.mainBundle().pathsForResourcesOfType("mp3", inDirectory: nil)
var filteredAudios = [AnyObject]()
var mp3names:NSMutableArray = []
var cellTapped:Bool = true
var currentRow = 0;
override func viewDidLoad() {
super.viewDidLoad()
for mp3 in mp3sPaths as Array<String> {
var mp3name = mp3.lastPathComponent
mp3names.addObject(mp3name.stringByDeletingPathExtension)
}
println(mp3names)
println(mp3sPaths)
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
if tableView == self.searchDisplayController!.searchResultsTableView {
return self.filteredAudios.count
} else {
return self.mp3names.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("AudioCell") as AudioCell
if tableView == self.searchDisplayController!.searchResultsTableView {
cell.titleLabel.text = filteredAudios[indexPath.row] as? String
} else {
cell.titleLabel.text = mp3names[indexPath.row] as? String
}
cell.playIcon.text = "▶️"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var mp3file = mp3sPaths[indexPath.row] as NSString
var mp3URL: NSURL = NSURL.fileURLWithPath(mp3file)!
var error: NSError?
audioPlayer?.stop()
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
audioPlayer?.play()
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? AudioCell {
cell.playIcon.text = "◾️"
}
var selectedRowIndex = indexPath
currentRow = selectedRowIndex.row
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? AudioCell {
cell.playIcon.text = "▶️"
}
audioPlayer?.stop()
var selectedRowIndex = indexPath
currentRow = selectedRowIndex.row
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == currentRow {
if cellTapped == false {
cellTapped = true
return 70
} else {
cellTapped = false
return 40
}
}
return 40
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
UIView.animateWithDuration(0.25, animations: {
cell.layer.transform = CATransform3DMakeScale(1,1,1)
})
}
func filterContentForSearchText(searchText: String) {
filteredAudios = mp3names as Array
filteredAudios.filter{($0 as NSString).localizedCaseInsensitiveContainsString("\(searchText)")}
}
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
self.filterContentForSearchText(searchString)
return true
}
func tableView(tableView: UITableView!, editActionsForRowAtIndexPath indexPath: NSIndexPath!) -> [AnyObject]! {
var shareAction = UITableViewRowAction(style: .Normal, title: "Share") { (action, indexPath) -> Void in
tableView.editing = false
println("shareAction")
}
shareAction.backgroundColor = UIColor.grayColor()
var doneAction = UITableViewRowAction(style: .Default, title: "Done") { (action, indexPath) -> Void in
tableView.editing = false
println("readAction")
}
doneAction.backgroundColor = UIColor.greenColor()
var deleteAction = UITableViewRowAction(style: .Default, title: "Delete") { (action, indexPath) -> Void in
tableView.editing = false
println("deleteAction")
}
return [deleteAction, doneAction, shareAction]
}
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
}
}
The screenshots are following:
Can anybody help, please?
I don't think you've shown all the code or the current code in your example.
I have implemented the same kind of thing in swift, and I know the table animations work remarkably well if handled properly.
If the cell frame itself is larger than the height shown by the table and it has content, you need to hide the overhanging subviews in the cell's contentContview when you collapse the cell via heightForRowAtIndexPath, or the visible subview overhanging cell frame will show through the other cells in the table after the cell is collapsed. That may be what's happening in your case but it's hard to tell for sure from your write-up.
But setting the alpha directly all at once looks tacky. You should animate the alpha property of the cell.contentView subviews that need to hide when the cell shrinks, and when the cell expands, using UIView.animateWithDuration() You want them to fade out as the row contracts and fade in as it expands.

how to change the height of a single cell when clicked?

I have to resize a single row of my tableView when clicked. How I can do this? Anybody could help me?
My view controller class:
class DayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var daysWorkPointTable: UITableView
override func viewDidLoad() {
super.viewDidLoad()
var nipName = UINib(nibName: "daysWorkPointsCell", bundle: nil)
self.daysWorkPointTable.registerNib(nipName, forCellReuseIdentifier: "daysWorkCell")
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView:UITableView!, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat {
return 75
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("daysWorkCell", forIndexPath: indexPath) as daysWorkPointsCell
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
}
First you have to keep track of the indexPath of currently selected cell in a property:
var selectedCellIndexPath: NSIndexPath?
It should be an optional, because you can have no cell selected.
Next lets declare heights for selected and unselected state (change the values to whatever you want):
let selectedCellHeight: CGFloat = 88.0
let unselectedCellHeight: CGFloat = 44.0
Now you have to implement tableView(_:, heightForRowAtIndexPath:):
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
}
return unselectedCellHeight
}
Now in your tableView(_:, didSelectRowAtIndexPath:) method you have to check wether the selected row or an unselected row has been tapped:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedCellIndexPath != nil && selectedCellIndexPath == indexPath {
selectedCellIndexPath = nil
} else {
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
if selectedCellIndexPath != nil {
// This ensures, that the cell is fully visible once expanded
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .None, animated: true)
}
}
The beginUpdates() and endUpdates() calls are giving you an animated height change.
If you want to change the duration of the height change animation you can wrap the beginUpdates() and endUpdates() calls in an animation block UIView.animationWithDuration(...) and set it to whatever value you want.
You can check out this sample project which demonstrates this code in action.

Resources