I am trying ot troubleshoot a tableView header (pink) that is animating a collapse. As the tableViewHeader height is shrinking the table view cells should pull up with the top of their tableView (orange). The beginning and end states are correct, but somehow the table view cells are animating up at a different rate. Something is clearly wrong here, I just can't seem to pinpoint what it is.
It appears to have something to do with the fact that I am using self sizing table view cells and tableView.rowHeight = UITableViewAutomaticDimension. If I use fixed height cells everything is fine.
Beginning State:
Middle State (Note cells already sliding under header):
Final State (Final state of layout is correct):
Here is the code that animates the collapse.
func collapseHeader() {
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: {
self.tableView.beginUpdates()
if let header = self.tableView.tableHeaderView as? TopicTableHeaderView {
header.setHeaderState(state: .collapsed)
}
self.sizeHeaderToFit()
self.view.layoutIfNeeded()
self.tableView.endUpdates()
}) { (bool) in
print("collapse completed")
}
}
func sizeHeaderToFit() {
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
if headerView.frame.height != height {
headerView.frame = frame
tableView.tableHeaderView = headerView
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
}
}
}
And the problem was simply where I was calling self.tableView.beginUpdates(). I moved that to the line directly above self.tableView.endUpdates() and that solved the problem.
Related
My situation:
I have a horizontal ScrollView containing a StackView.
Inside this StackView there are some Views, that can be expanded/collapsed.
When I want to expand one of these Views, I first unhide some subViews in the View. After that I need to change the height of the ScrollView based on the new height of this View.
But this is not working...
I try this code:
UIView.animate(withDuration: 0.3) { [self] in
// Toggle hight of all subViews
stackView.arrangedSubviews.forEach { itemView in
guard let itemView = itemView as? MyView else { return }
itemView.toggleView()
}
// Now update the hight of the StackView
// But here the hight is always from the previous toggle
let height = self.stackView.arrangedSubviews.map {$0.frame.size.height}.max() ?? 0.0
print(height)
heightConstraint.constant = height
}
This code nicely animates, but always to the wrong height.
So the ScrollView animates to collapsed when it should be expanded and expanded when it should be collapsed.
Anyone with on idea how to solve this?
The problem is that, whatever you are doing here:
itemView.toggleView()
may have done something to change the height a view, but then you immediately call:
let height = self.stackView.arrangedSubviews.map {$0.frame.size.height}.max() ?? 0.0
before UIKit has updated the frames.
So, you can either track your own height property, or...
get the frame heights after the update - such as with:
DispatchQueue.main.async {
let height = self.stackView.arrangedSubviews.map {$0.frame.size.height}.max() ?? 0.0
print("h", height)
self.scrollHeightConstraint.constant = height
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
I Need to adjust a child collection view height when I've calculated what it's parent scrollView should be. The height calculation produces the correct value but I can't get the "Grand child" collectionView to adjust itself.
All the constraints inside the storyBoard are tied to the scrollView container view height so when I adjust it's value they all should follow in line and adjust themselves.
The flow is:
UIScrollView -> UICollectionView -> UICollectionView
My calculation is:
var count: CGFloat = 370
guard let currentLegs = active.legs![cell].feet else { return }
for _ in currentLegs {
count += 70
}
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
if count > 540 {
self.scrollView.contentSize.height = count
} else {
self.scrollView.contentSize.height = 540
}
}, completion: nil)
print(count)
The above calculates the height and defaults to another value if it's smaller than I need.
I've tried setting the scrollview.layoutIfneeded() & setNeedsLayout() but these cause the collectionView to redraw itself resulting in a crash.
Is it possible to adjust a "grand child" collection view from the top level?
Thanks
Try this
#IBOutlet weak var scrollViewHeightConstarint: NSLayoutConstraint!
.....
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.scrollViewHeightConstarint.constant = <#WhatYouNeed#>
self.view.layoutIfNeeded()
}
I have UITableView, and I put container view on top as a header like this:
And everything work well.
Now I would like to hide this container header view, when tap in the cell.
My first attempt was :
UIView.animate(withDuration: 0.3, animations: {
self.containerView.frame.size.height = 0
})
witch hide the view, but don't bring cells up, what it is expected and normal, because I only change frame for container view, and not for other views.
Then I try to add constraints in storyboards, but for some reason I can't set them.
Why is that? And how do I achieve to hide container view, and bring all other cells to the top.
Try this
UIView.animate(withDuration: 0.5) {
let headerView = tableView.tableHeaderView!
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
headerView.frame.size.height = 0
tableView.tableHeaderView = headerView
}
I'm trying to animate a table view cells on the initial load. The animation works fine for all the cells, except for the last one at the bottom of the table which refuses to animate.
This is because the UITableView.visibleCells does not return this cell at the end. (it returns cells 0-12, the last cell is at index 13 and is clearly visible)
Here is the code. Is there anything I can do to ensure all the cells get animated?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
let cells = tableView.visibleCells
let tableHeight: CGFloat = clubsTable.bounds.size.height
for i in cells {
let cell: UITableViewCell = i as UITableViewCell
cell.transform = CGAffineTransformMakeTranslation(0, tableHeight)
}
var index = 0
for a in cells {
let cell: UITableViewCell = a as UITableViewCell
UIView.animateWithDuration(1.5, delay: 0.05 * Double(index), usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
cell.transform = CGAffineTransformMakeTranslation(0, 0);
}, completion: { (complete) in
})
index += 1
}
}
That is because the height of your table is less than the height of 13 cells. Thus it animates 12 cells. What you can do is to make the table height bigger in 30px (or any px until it contains 13 cells), and after the animation is done, change the height of the tableView back to normal.
after you call let cells = tableView.visibleCells can't you just do something like
let indexPath = NSIndexPath(forRow: cells.count, inSection: 0)
cells.append(tableView.cellForRowAtIndexPath(indexPath))
Going off memory on those function signatures but you get the point.
I am building a form dynamically that has various types of UI elements for each UITableViewCell.
There is one UITableViewCell that contains a UITextView and I want to be able to maintain visibility when showing the keyboard. I have looked at the other similar questions, but have been unable to find a solution.
I wrote the Swift version of what is recommended by: Apple's Managing Keyboard
It does not work for two reasons.
1.) The Keyboard notification is fired before the TextViewWillBeginEditing.
2.) The frame of the UITextView is in relation to the superview which is the UITableViewCell, so the check is wrong.
Here is my current code:
func adjustTableViewForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewEndFrame = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
if notification.name == UIKeyboardWillHideNotification {
self.tableView.contentInset = UIEdgeInsetsZero
} else {
self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
rect = self.view.frame;
rect!.size.height -= keyboardViewEndFrame.height
}
self.tableView.scrollIndicatorInsets = self.tableView.contentInset
}
func textViewDidBeginEditing(textView: UITextView) {
self.activeTextView = textView;
if(activeTextView != nil){
// This check does NOT work due to the TextView's superview is the cell.
if(!CGRectContainsPoint(rect!, (activeTextView?.frame.origin)!)){
self.tableView.scrollRectToVisible((activeTextView?.frame)!, animated: true)
}
}
}
This works in terms of being able to scroll to all cells, but I also want to make sure the UITextView is not hidden by keyboard.
You may be able to use the responder chain to solve this. In your notification handler, try calling isFirstResponder() on your text view; if it returns true, then you can call the table view-scrolling code from there.
I had a similar issue with a (albeit static cell) form I was building, I was able to use the scrollToRowAtIndexPath method to update the tableview's row positioning to keep my text view on screen.
func textViewDidBeginEditing(textView: UITextView) {
if textView == notesTextView {
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 3), atScrollPosition: .Top, animated: true)
}
}
It does require knowledge of the index path section/row and the text view reference in case of multiples, but might be of use?
scrollRectToVisible should also work, but sounds like you have to convert the textview's frame to coordinate system of the scrollview using convertRect:toView or similar first.
Hope this helps - Cheers.