What I want to do (verbatim): Hold a tableview cell for specified amount of time. Once it reaches that time period, the cell height increases gradually. When I release my finger, the cell height stops growing.
What I have:
I have several tableViewCells. After pressing on a cell for a specified amount of time using:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("longPress:"))
longPressRecognizer.minimumPressDuration = 1.0s
longPressRecognizer.delegate = self
self.view.addGestureRecognizer(longPressRecognizer)
I wanted to increase the cell height. But I couldn't do that without knowing at which row the touch was located, so I got up to here:
func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizerState.began {
let touchPoint = longPressGestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
//let cell = tableView.cellForRow(at: indexPath)
}
}
}
I thought about controlling the rowHeight, but that's solely within the tableView functions so I did not know how to call it.
I am unsure how to proceed. And I am not looking for anything that has to do with .beginUpdates and .endUpdates because I want the cell growth to be gradual and preferably animated.
Any help would be greatly appreciated, as I have been searching for answers to this specific problem for quite a long time.
code that includes the rowHeight declaration:
override func viewDidLoad() {
super.viewDidLoad()
// let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("longPress:"))
// longPressRecognizer.minimumPressDuration = 1.0 // 1 second press
// longPressRecognizer.delegate = self
// self.view.addGestureRecognizer(longPressRecognizer)
tableView.delegate = self
tableView.dataSource = self
tableView.allowsSelection = false
self.tableView.reorder.delegate = self
view.backgroundColor = UIColor(red:0.64, green:0.93, blue:0.78, alpha:1.0)
tableView.backgroundColor = UIColor.clear
tableView.rowHeight = 84
tableView.rowHeight = UITableViewAutomaticDimension
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
I don't like to explain things in details using words, so instead of a long answer, I will just include a relatively short code (available also as gist) that contains comments explaining the basics. I haven't really paid a lot of attention to architecture and clean code, I just focused on getting the task done. Take the code as such - try to make it better in your codebase.
Anyway, the code should be pretty clear and self-explanatory, but I would like to just sketch a big picture for you before you dive into it. In the solution, I keep the heights in an array of CGFloat
(variable cellHeights
) and modify a height for a given row by changing the corresponding height in an array. That array serves as a basis for heightForRowAt
implementation.
When long press begins, I start a timer, that every 0.1 seconds updates the height for the selected row by modifying the height in the cellHeights
array and telling the tableView
to redraw itself. That happens until the limit is reached for that given row and then I simply cancel (invalidate) the timer.
If the long press ends before the limit height is reached, I just explicitly cancel the timer, so that the cell stops enlarging when the user releases the press.
And that's it. I suggest you take the EnlargingCellsOnLongPressController
gist (or the one below, it is the same code), try it on your own device, read the code along with the comments, and I believe you should be able to implement it in your own situation.
import UIKit
class EnlargingCellsOnLongPressController: UITableViewController {
// this will hold the indexPath of the cell we are currently enlarging
var enlargingIndexPath: IndexPath?
// dummy data model
var modelItems: [String] = []
// we will need to keep the heights for each particular cell (since any can be resized)
var cellHeights: [CGFloat] = []
// some height limit, I will set it for myself to 200
let limitHeight = CGFloat(200)
// the enlarging itself will be done by a timer
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// nothing special here
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.estimatedRowHeight = 100
tableView.allowsSelection = false
// creating some dummy data, 30 items, and for each a cell height that will start at 100
for index in 0..<30 {
modelItems.append("Item \(index)")
// by default we will start with 100
cellHeights.append(CGFloat(100))
}
// please, use swift 4 and the new #selector syntax
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer:)))
longPressRecognizer.minimumPressDuration = 1
self.view.addGestureRecognizer(longPressRecognizer)
}
// following three methods should be clear
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return modelItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = modelItems[indexPath.row]
return cell
}
// for height for row we will return a specific height from cellHeights array
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath.row]
}
@objc func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizerState.began {
let touchPoint = longPressGestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
//when the press starts on a cell, we will keep the indexPath for the cell
self.enlargingIndexPath = indexPath
// and turn on enlarging
self.startEnlarging()
}
} else if longPressGestureRecognizer.state == .ended {
// when the press is ended, we can stop enlarging
stopEnlarging()
}
}
func startEnlarging() {
// interval 0.1 second seems smooth enough (redraw seems to be animated anyway)
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] (timer) in
guard let strongSelf = self, let enlargingIndexPath = self?.enlargingIndexPath else { return }
let oldHeight = strongSelf.cellHeights[enlargingIndexPath.row]
// since the timer repeats every 0.1 second, this will enlarge the cell 20 points per second till limit
// in one cycle I will enlarge the cell by two points
let newHeight = oldHeight + 2
if newHeight < strongSelf.limitHeight {
// if the newHeight did not reach limit,
// update height and redraw tableView
strongSelf.cellHeights[enlargingIndexPath.row] = newHeight
strongSelf.tableView.beginUpdates()
strongSelf.tableView.setNeedsLayout()
strongSelf.tableView.endUpdates()
} else {
// reached maximum size, just cancel the timer
strongSelf.stopEnlarging()
}
})
}
func stopEnlarging() {
// this just cancels the timer
timer?.invalidate()
}
}
UPDATE
For the sake of completeness, I have create a gist using autolayout and UITableViewAutomaticDimension
, if you ever decide to use that instead of heightForRowAt
. But the principle is the same.
Declare property for height of the cell and return this property in the tableviews's delegate method heightForRowAtIndexPath method
var heightForCell = 100
func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizerState.began {
let touchPoint = longPressGestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
self. heightForCell = // height you want to set
// now reload cell again
}
}
}