Animate Insertion & Deletion of UITableViewCell via UISwitch - uitableview

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.

Related

Detect When UITableViewCell is About To Leave The UITableView

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.

Scroll to last element of TableView take too much time

I'm trying to simulate a Whatsapp Chat any cell will have an image (for tail of the bubble), a bubble which is just View with color and some corner radius and a label which will represent the text of the message.
I've put a print before and after the call
self.messagesTableView.reloadData()
Once the after print is called tableView keeps some time doint I don't know what till the data is shown. And same happens with Insert row at indexpath, it takes some time till show the insert animation.
func displayMessages(viewModel: GroupChatMessages.GetChatMessages.ViewModel) {
let displayedMessage = viewModel.displayedMessages
print ("i'm here!")
messages = displayedMessage!
//self.messagesTableView.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)
self.messagesTableView.reloadData()
print ("i'm here2!")
firstTime = false
self.setVisible(hiddenTableView: false, hiddenChatLoader: true)
self.scrollToLastMessage(false)
self.messagesLoaded = true
}
I've tried to do dispatched with queue, and the commented line before reloadData(), but nothings works and nothing represent a significative time.
Maybe could be for the image of the bubble? I don't know. I have this image saved on Assets, so I'm not downloading it from internet.
self.setVisible just hide the loader and show the tableView but I've tried too moving it up and nothings changes. Any further information you need let me know. Thanks!
EDIT:
Well I've seen that the problem comes from the scroll to last cell, this is where it takes the major part of the time.
func scrollToLastMessage(animated: Bool) {
let section = 0
let lastItemIndex = self.messagesTableView.numberOfRowsInSection(section) - 1
let indexPath:NSIndexPath = NSIndexPath.init(forItem: lastItemIndex, inSection: section)
self.messagesTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated)
self.scrollDownButton.hidden = true
}
There is a posibility to optimize that scroll, because I have to do a Scroll because once the data is loaded, the first I've see is the top row of the tableView, but I would like to see the bottom one (last). Thanks!
methods like reloadData() should be considered as UI methods and it's mandatory to call them in main thread:
DispatchQueue.main.async { tableView.reloadData() }
It's better not to use reloadData() function unless a significant amount of cells need to refresh or data source has been changed instead use this method to add new rows:
tableView.insertRows(at: [IndexPath], with: UITableViewRowAnimation)
and for refreshing cell:
tableView.reloadRows(at: [IndexPath], with: UITableViewRowAnimation)
also if the cell has a considerable amount of images and rendering, use this code to make scrolling faster:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
// ADD THESE TWO LINE
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
}
Using these ways will boost loading speed significantly
Finally the solution that I've found to avoid dying while waiting scrolling to last element any single time, is swapping orientation of table
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
cell.transform = CGAffineTransformMakeRotation(M_PI);
Now headerView and footerView are reversed. For exemple, if you would like insert rows at (visually) at the bottom of the TableView with this configuration you should add it at position 0 forRow: 0 atSection: "WhereYouAre". This way when you add new element, no scroll is needed, because scroll is automatically. Amazing and strange answer IMHO.
I've found this solution here:
Solution Link
#Christos Hadjikyriacou solved there.

UITableViewCell height is not fitted when hiding UIView inside the cell using AutoLayout

I have been struggling this issue for 3 days and still can not figure it out. I do hope anyone here can help me.
Currently, i have an UITableView with customized cell(subclass of UITableViewCell) on it. Within this customized cell, there are many UILabels and all of them are set with Auto Layout (pining to cell content view) properly. By doing so, the cell height could display proper height no matter the UILabel is with long or short text.
The problem is that when i try to set one of the UILabels (the bottom one) to be hidden, the content view is not adjusted height accordingly and so as cell.
What i have down is i add an Hight Constraint from Interface Builder to that bottom label with following.
Priority = 250 (Low)
Constant = 0
Multiplier = 1
This make the constrain with the dotted line. Then, in the Swift file, i put following codes.
override func viewDidLoad() {
super.viewDidLoad()
//Setup TableView
tableView.allowsMultipleSelectionDuringEditing = true
//For tableView cell resize with autolayout
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
}
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! RecordTableViewCell
cell.lbLine.hidden = !cell.lbLine.hidden
if cell.lbLine.hidden != true{
//show
cell.ConstrainHeightForLine.priority = 250
}else{
//not show
cell.ConstrainHeightForLine.priority = 999
}
//tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
return indexPath
}
The tricky thing is that when i call tableView.reloadRowAtIndexPaths(), the cell would display the correct height but with a bug that it has to be trigger by double click (selecting) on the same row rather than one click.
For this, i also try following code inside the willSelectRowAtIndexPath method, but none of them is worked.
cell.contentView.setNeedsDisplay()
cell.contentView.layoutIfNeeded()
cell.contentView.setNeedsUpdateConstraints()
Currently the result is as following (with wrong cell Height):
As showed in the Figure 2, UILabel 6 could be with long text and when i hide this view, the content view is still showing as large as it before hiding.
Please do point me out where i am wrong and i will be appreciated.
I finally change the code
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
to the following
tableView.reloadData()
Then, it work perfectly.
However, i don't really know the exactly reason on it. Hope someone can still comment it out.

incorrect order of Accessibility in UICollectionView

So I have a UICollectionView in a UIViewController which is one of the root view controllers in the tab bar. I set a contentInset for the UICollectionView so I can Add a Label to the top of the collectionView, at which point it would mean that the UILabel is part of the collectionView but is not part of the headerView of the collectionView. To achieve the addition of the UILabel to the UICollectionView, I use
collectionView.addSubview(theLabel)
and I turn voice over on and run the application. what happens is that the voiceover goes through all the UICollectionViewCells in the correct order all the way to the last CollectionViewCell to begin, then goes to the Label which is at the top of the collectionView and then goes to the tabBar. I tried the answer in this Change order of read items with VoiceOver, but had no luck, this solution did change the order of
self.accessibilityElements
to the way I want, except the voice over doesn't really follow the order in self.accsibilityElements and I am not really sure what is going on, has anyone come across the same trouble with the accessibility order being screwed up because "addsubView" was used on the UICollectionView. IF (and I say IF, because I don't think anyone would have added a subView to a collectionView this way) anyone has any thoughts please help me out here, been stuck with this bug the longest time.
Edit
class CollectionViewSubViewsAddedByTags: UICollectionView {
override func awakeFromNib() {
super.awakeFromNib()
}
var accessibilityElementsArray = [AnyObject]()
override var accessibilityElements: [AnyObject]?{
get{
return accessibilityElementsArray
}
set(newValue) {
super.accessibilityElements = newValue
}
}
override func accessibilityElementCount() -> Int {
return accessibilityElementsArray.count
}
override func accessibilityElementAtIndex(index: Int) -> AnyObject? {
return self.accessibilityElementsArray[index]
}
override func indexOfAccessibilityElement(element: AnyObject) -> Int {
return (accessibilityElementsArray.indexOf({ (element) -> Bool in
return true
}))!
}
override func didAddSubview(subview: UIView) {
super.didAddSubview(subview)
accessibilityElementsArray.append(subview)
accessibilityElementsArray.sortInPlace { $0.tag<($1.tag)}
}
override func willRemoveSubview(subview: UIView) {
super.willRemoveSubview(subview)
if let index = (accessibilityElementsArray.indexOf({ (element) -> Bool in
return true
})) {
accessibilityElementsArray.removeAtIndex(index)
}
}
}
Thanks,
Shabri
I've run into the same issue - I'm using a top inset on our UICollectionView to allow room for a header that slides on/off screen with scroll. If I use Voice Over with this layout then the entire system gets confused and the focus order is incorrect. What I've done to get around this is use an alternate layout when VO is activated - instead of placing the header over the collection view with an inset, I place the header vertically above the collection view and set 0 top inset on the collection view.

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