Detect When UITableViewCell is About To Leave The UITableView - ios

I want to animate a UITableViewCell whenever it is about to leave the view. I know that there is a didEndDisplayingcell function, but by then, it is too late to animate the cell. Becuase it's gone. I basically want to cell to scale down using...
UIView.animate(withDuration: 0.2) {
self.cellView.transform = CGAffineTransform(translationX: self.view.frame.size.width, y: 0)
}
...and whenever it comes back on the screen, scale it back to normal.
Any ideas?

Not a complete solution, but this might get you there:
self.tableView.indexPathsForVisibleRows
Use this to find whats on the screen.
for path in self.tableView.indexPathsForVisibleRows! {
//check if it's past a point on the screen
//animate if needed
}
Check for these conditions in
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
// call the function to check if cells pass a threshold
}
The last bit will be setting it up so cells are fully visible on first load, but after that they start off in the state to be animated in.

The best way to approach this would be to check when the cell is finished being displayed via the UITableViewDelegate method:
func tableView(UITableView, didEndDisplaying: UITableViewCell, forRowAt: IndexPath)
Tells the delegate that the specified cell was removed from the table.

Related

How to pass tap gesture through point on screen that is not in full-screen UITableView's UIEdgeInsets:

I have created a screen in which I have a full-screen UITableView, I set UIEdgeInsets to it, which I have configured as follows:
categoriesTableView.contentInset = UIEdgeInsets.init(top: HEADER_VIEW_HEIGHT, left: 0, bottom: 0, right: 0)
where HEADER_VIEW_HEIGHT is CGFloat = 160.
This allowed me to add a "header view" to the UITableView which is getting covered when I start scrolling the UITableView (...and not getting stuck above the UITableView, as a real header view will).
The Problem: The problem is that I need to have 3 clickable views in the header view, So I designed 3 views in the storyboard and configured Tap Gesture Recognizers on them. But when I try to use them the tap gestures are not passed throw the UITableView even though I see those views on screen (as a result of the contentInset configuration). The only way I can make them tapable/clickable is if I set User Interaction Enabled to false on the UITableView (which I can't do because I need the UITableView to be draggable and clickable as well).
The Question: How would I pass the tap events to the lower "header view" clickable parts when it's not covered by the UITableView as a result of the contentInset setting?
Here is the UI image, in it you can see that there is a full-screen UITableView, behind it there a view with 3 subviews that contains 3 favorite items which I can present to the user for an easier access. when the screen starts, there is a contentInset for the UITableView hence the user can see those easy access options and press them (which he can't do right now). When the user starts scrolling, the UITableView goes on top of the layout with the 3 views and the user able to scroll the list in a full screen. kind of like a parallax effect.
I have a very ugly solution for this..
Now you have a table view and below that there is a header view right? Add one more view on top of table view which contains 3 sub views in it and all transparent in colour.
Position this newly added subview same as that of header view (Either by programmatically giving same frame as that of header view or by connecting its top, left, bottom and right constraints to the header view). Similarly position the 3 sub views of this new view as same as that of the subviews inside the header view. And give tap gesture to the subviews of this new view. So our user will think he is tapping the header view, whereas he is actually tapping in this invisible view.
And if you want to get touch in the table view once it scrolls up and cover the header view, then you can use one of these two..
Call the UIScrollViewDelegate delegate method scrollViewDidScroll() and inside if scrollView.contentOffset.y >= 160, set the User Interaction Enabled to false for this newly added view and revert that back to true if scrollView.contentOffset.y < 160.
Or from inside scrollViewDidScroll(), assign outletOfTopConstraintOfYourNewView.constant = -(scrollView.contentOffset.y), so that the new view will also move up according to the scroll, thereby changing its visible tappable area.
Another not so elegant idea is..
Instead of giving contentInset, add one more section in this table view at the 0th index with only one cell whose height is 160, user interaction enabled is false and colour is transparent. Then you don't have to worry about the logic in scrollViewDidScroll().
Update:
Below solution is not perfect but it may give you a direction to start with.
A) Put the top constraint of tableView top to bottom constraint of headerview.
B) Create IBOutlet of height constraint of headerview in your ViewController
C) Listen to tableview's ScrollViewDidScroll and put code like this
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = scrollView.contentOffset.y
if y > 50{
if heightConstant.constant != 0{
view.layoutIfNeeded()
heightConstant.constant = 0
UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}else{
view.layoutIfNeeded()
heightConstant.constant = 160
UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
Here 50 is a point form where it should start animating. It is somewhat similar to Collapsable Toolbar in Android. Just make sure headerView.isClipsToBounds = true.
Convert your headerview into a UITableViewCell and in change your UITableViewDataSource as
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 : yourList.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
// set your header view here
}else{
// Your normal cell configuration
}
}
}
With this, your headerview will become a part of table view but as a UITableViewCell thus it will not behave like sticky header like the normal one.

Swift: UICollectionView Paging Enabled

I have a UICollectionView with 3 full-screen cells and want to know when a cell is focused full-screen upon swipe. The cellForItemAtIndexPath.isFocused method isn't working for me. Is this the correct use of isFocused method or should I do it another way?
What ever, I can get from your question. I think you want to know on which page it is. So, Here is how i did it:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in colViewSol.visibleCells {
let indexPath = colViewSol.indexPath(for: cell)
// here add the code whatever you want to do
}
}

Animate Insertion & Deletion of UITableViewCell via UISwitch

I have a UITableView that I am using to display settings for my application. What I want to achieve is animation of two UITableViewCells when a UISwitch's value is changed.
A couple of nice examples are the Wi-Fi settings in iOS's Settings. When you turn the Wi-Fi off, the sections below that cell fade out and upward until they are gone. Inversely, they fade in when the Wi-Fi is enabled. Also, Tweetbot 4 does this beautifully in the Display settings when choosing a setting under Theme. When enabled, two cells move upward until gone and then one cell moves downward to take the place of the other two cells.
I was able to find a tutorial with gifs for achieving the hiding and display of cells, however, it is without a nice animation. Instead, the cells just disappear and reappear abruptly.
My tableView:heightForRowAtIndexPath: method looks like this:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 1 && authenticationSettingsSwitch.on == false {
return 0
}
if indexPath.row == 2 && authenticationSettingsSwitch.on == false {
return 0
}
return 44
}
If the switch is on, the two cells are visible, with a height of 44. If the switch is off, the two cells are collapsed with a height of 0.
The aforementioned takes place in my UISwitch's action method:
#IBAction func authenticationSettingsSwitchFlipped(sender: UISwitch) {
let requireAuthentication = sender.on == true ? true : false
defaults.setBool(requireAuthentication, forKey: Settings.Authentication.rawValue)
defaults.synchronize()
if requireAuthentication {
delay(0.3) { () -> Void in
let authenticationNavigationController = self.storyboard?.instantiateViewControllerWithIdentifier("authenticationNavigationController") as! UINavigationController
let authenticationViewController = authenticationNavigationController.topViewController as! AuthenticationViewController
authenticationViewController.settingsDelegate = self
self.presentViewController(authenticationNavigationController, animated: true, completion: nil)
}
} else {
tableView.reloadData()
}
}
I have also looked at tableView:insertRowsAtIndexPaths:withRowAnimation, however, I keep getting crashes for trying to call this method without using a data source since the cells are static.
Below are a couple of screenshots that may help you see more clearly what I am wanting:
As stated above, I can get the cells to appear and disappear via a UISwitch in the first cell (top set of screenshots), but they do so abruptly. In the bottom set of screenshots (Tweetbot 4), the animation is gorgeous and smooth.
Can anyone point me in the direction of how to achieve what I am after?
I found a solution to enable a nice animation for showing/hiding static UITableViewCells here.

indexPathForRowAtPoint.row is always returning zero for every cell in a tableView

I have tableView cells with some custom views in them. When the custom views are touched I need to store the indexPath.row of the cell that the touched view is inside of. This is how I am currently getting the indexPath:
#IBAction func bulletGroupPressed(sender: AnyObject) {
let center = sender.center as CGPoint
let indexPath = self.exercisesTableView.indexPathForRowAtPoint(center)!
self.exercisesTableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
println(indexPath.row)
}
I suspect it has something to do with the ! on the indexPath line, but am not sure exactly what it is. Why does indexPath.row always give me zero on every cell?
I can't give you a coded answer in swift, but the point needs to be stated in the coordinate system of the table. As you have it, the center is stated in the coordinate system of the sender's parent, probably the table view cell. Use the method convertPoint:toView: to fix the coordinate system.
Alternatively, you might consider creating a method like the one shown in my answer to this question. From any view (in your case, the sender), it will walk up the view hierarchy to the first UITableViewCell and report that cell's index path.

Custom UIViewController transition where UITableViewCell grows to full screen and back?

I haven't been able to find any examples of this online. How can I achieve the following effect? Instead of the standard slide-to-left effect when tapping a table row, I'd like the view controller transition animation to look like the following:
User taps a cell in the table
The cell starts growing to fill the screen, pushing other rows above and below it "offscreen".
As the cell grows, cells elements (text, images, etc.) cross-fade into the new view's contents until the new view completely fills the screen.
I'd like to also be able to interactively transition back into the table view by dragging up from the bottom edge, such that the reverse of the above is achieved. i.e. view starts shrinking back into a normal table view cell as the "offscreen" cells animate back into position.
I've thought about taking a snapshot of the table and splitting it up at the points above and below the cell, and animating these snapshots offscreen as part of a custom view controller transition. Is there a better way? Ideally I'd like to not take snapshots, as I may want to have animations, etc., still happening in the table view rows as they fade offscreen.
First thing first, i have seen your post today and his is written in swift programming language.
the follwing code is to expand the selected cell to the full screen, where the subviews will fade out and background image will expands.
First complete cellForRowAtIndexPath, then in didSelectRowAtIndexPath,
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
isSelected = true
selectedCellIndex = indexPath.row
tableView.beginUpdates()
var cellSelected: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellSelected.frame = tableCities.rectForRowAtIndexPath(indexPath)
println("cell frame: \(cellSelected) and center: \(cellSelected.center)")
let newCell = cellSelected.frame.origin.y - tableOffset
println("new cell origin: \(newCell)")
var tempFrame: CGRect = cellSelected.frame
variableHeight = cellSelected.frame.origin.y - tableOffset
tempFrame.size.height = self.view.frame.height
println("contentoffset: \(tableView.contentOffset)")
let offset = tableView.contentOffset.y
println("cell tag: \(cellSelected.contentView.tag)")
let viewCell: UIView? = cellSelected.contentView.viewWithTag(cellSelected.contentView.tag)
println("label: \(viewCell)")
viewCell!.alpha = 1
UIView.animateWithDuration(5.0, delay: 0.1, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
tableView.setContentOffset(CGPointMake(0, offset + self.variableHeight), animated: false)
tableView.contentInset = UIEdgeInsetsMake(0, 0, self.variableHeight, 0)
tableView.endUpdates()
cellSelected.frame = tempFrame
viewCell!.alpha = 0
println("contentoffset: \(tableView.contentOffset)")
}, completion: nil)
}
then update your heightForRowAtIndexPath, as
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if isSelected && (selectedCellIndex == indexPath.row) {
return self.view.frame.height
}else {
return 100
}
}
Ignore this if already solved. and this might be helpful to others.

Resources