Animate Height of UITableView - ios

I am trying to animate the tableviews height when the collection view scrolls. I call the animation function from scrollViewWillBeginDragging. Then I execute an animation block:
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if expanded == false && scrollView == IBcalendarCollectionView {
expanded = true
expandCollectionView()
}
}
func expandCollectionView() {
let screenSize: CGRect = UIScreen.mainScreen().bounds
UIView.animateWithDuration(3, delay: 0, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in
self.IBcalendarTableView.frame = CGRectMake(self.IBcalendarTableView.frame.origin.x, screenSize.height/2, self.IBcalendarTableView.frame.width, screenSize.height/2)
}) { (success) -> Void in
print(self.IBcalendarTableView.frame.origin.y)
}
}
Whats happening is the TableView animates up to the status bar then returns to its original position. I want it to animate down to half the size of the screen. The animation block is only executed once because I set a boolean when the animation function is called.

you say you set a boolean when the animation block is called, is that so the animation is not being called again and again? I also don't see that in your code.
This should work fine.
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
let mainScreen = UIScreen.mainScreen().bounds
if (self.IBcalendarTableView.frame.origin.y != mainScreen.height / 2) {
var frame = self.IBcalendarTableView.frame
frame.origin.y = mainScreen.height / 2
frame.size.height = frame.origin.y
UIView.animateWithDuration(0.5) { () -> Void in
self.IBcalendarTableView.frame = frame
}
}
}
I set a lower animation speed, 3 seconds seems like a lot... I also built the frame differently, I just do it because I find it easier to read.
Please let me know if there is anything else I can do to help.

The issue is with autolayout. You have to also set the height constraint of the tableview in order to keep the tableview at the new height/origin.
For anyone else who may have this issue:
Dynamic UITableView height
This answer helped me solve this problem. My fixed code looks like this:
func expandCollectionView() {
let screenSize: CGRect = UIScreen.mainScreen().bounds
var frame = self.IBcalendarTableView.frame
frame.origin.y = screenSize.height / 2
frame.size.height = frame.origin.y
UIView.animateWithDuration(0.5) { () -> Void in
self.IBcalendarTableView.frame = frame
self.IBtableViewHeightConstraint.constant = screenSize.height/2
}
}

Related

How to remove blank space inside WKScrollView after change WKWebview frame height?

How to remove the blank area after change WKWebview's frame height?
I want change webview's height when keyboard show up, but after the frame height changed, there are an extra space below the HTML body. And the height of this extra blank area is about the height of keyboard.
Here is the code snippet:
NotificationCenter.default.rx
.notification(UIApplication.keyboardDidShowNotification)
.subscribe(onNext: { [weak self] keyboardConfig in
if let userInfo = keyboardConfig.userInfo{
let keyboardBounds = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue;
self?.webView.snp.remakeConstraints({ make in
make.left.right.top.equalToSuperview();
/// remake from screen height to below height.
make.height.equalTo(UIScreen.main.bounds.size.height - keyboardBounds.size.height);
});
}
});
Finally I still don't know how to remove this extra blank area, but I have figure out a workaround.
Since WKScrollView is a subclass of UIScrollView, so the UIScrollView Delegate also works for WKScrollView;
Therefore the contentOffset can be force reseted in the UIScrollView scrollViewDidScroll delegate method. Code snippet is provided below.
/// Change webview frame when keyboard appear.
NotificationCenter.default.rx
.notification(UIApplication.keyboardDidShowNotification)
.subscribe(onNext: { [weak self] keyboardConfig in
if let userInfo = keyboardConfig.userInfo, let this = self {
let keyboardBounds = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue;
this.webView.snp.remakeConstraints({ make in
make.left.right.top.equalToSuperview();
/// remake from screen height to below height.
make.height.equalTo(UIScreen.main.bounds.size.height - keyboardBounds.size.height);
});
let originalOffset = this.webView.scrollView.contentOffset;
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
this.webView.scrollView.contentOffset = originalOffset;
}
}
});
/// Force reset contentOffset when the contentOffset is out of HTML Body
extension YourWebviewVC: UIScrollViewDelegate{
internal func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.adjustWebviewOffsetFor(scrollView);
}
private func adjustWebviewOffsetFor(_ scrollView: UIScrollView, animated: Bool = false){
let currentY = scrollView.contentOffset.y + scrollView.frame.height;
if currentY > scrollView.contentSize.height{
let maxOffset = scrollView.contentSize.height - scrollView.frame.height;
scrollView.setContentOffset(CGPoint(x: 0, y: maxOffset), animated: animated);
}
}
}
/// Don't forgot set webview scroolView's delegate, for example
self.webView.scrollView.delegate = self;
self.webView.scrollView.showsVerticalScrollIndicator = false;

Manually scrolling UIScrollView which is animated by UIViewPropertyAnimator

I have a UIScrollView which scrolls automatically by setting its content offset within via a UIViewPropertyAnimator. The auto-scrolling is working as expected, however I also want to be able to interrupt the animation to scroll manually.
This seems to be one of the selling points of UIViewPropertyAnimator:
...dynamically modify your animations before they finish
However it doesn't seem to play nicely with scroll views (unless I'm doing something wrong here).
For the most part, it is working. When I scroll during animation, it pauses, then resumes once deceleration has ended. However, as I scroll towards the bottom, it rubber bands as if it is already at the end of the content (even if it is nowhere near). This is not an issue while scrolling towards the top.
Having noticed this, I checked the value of scrollView.contentOffset and it seems that it is stuck at the maximum value + the rubber banding offset. I found this question/answer which seems to be indicate this could be a bug with UIViewPropertyAnimator.
My code is as follows:
private var maxYOffset: CGFloat = .zero
private var interruptedFraction: CGFloat = .zero
override func layoutSubviews() {
super.layoutSubviews()
self.maxYOffset = self.scrollView.contentSize.height - self.scrollView.frame.height
}
private func scrollToEnd() {
let maxOffset = CGPoint(x: .zero, y: self.maxYOffset)
let duration = (Double(self.script.wordCount) / Double(self.viewModel.wordsPerMinute)) * 60.0
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.scrollView.contentOffset = maxOffset
}
animator.startAnimation()
self.scrollAnimator = animator
}
extension UIAutoScrollView: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// A user initiated pan gesture will begin scrolling.
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
self.interruptedFraction = scrollAnimator.fractionComplete
scrollAnimator.pauseAnimation()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
scrollAnimator.startAnimation()
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
scrollAnimator.startAnimation()
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
switch scrollView.panGestureRecognizer.state {
case .changed:
// A user initiated pan gesture triggered scrolling.
if let scrollAnimator = self.scrollAnimator {
let fraction = (scrollView.contentOffset.y - self.maxYOffset) / self.maxYOffset
let boundedFraction = min(max(.zero, fraction), 1)
scrollAnimator.fractionComplete = boundedFraction + self.interruptedFraction
}
default:
break
}
}
}
Is there anywhere obvious I'm going wrong here? Or any workarounds I can employ to make the scroll view stop rubber banding on scroll downwards?
You can add tap Gesture Recognizer and call this function,
extension UIScrollView {
func stopDecelerating() {
let contentOffset = self.contentOffset
self.setContentOffset(contentOffset, animated: false)
}
}

Collection view header flickers when resizing at some values

I have a custom UICollectionView layout that resizes when the user scrolls. As the header shrinks at one point it begins to flicker.
I'm guessing the issue is that when the header shrinks the collection view thinks it's out of frame and perhaps dequeues it but then it calculates that it is in frame and re-queues it which might be what's causing the flicker.
class CustomLayout: UICollectionViewFlowLayout, UICollectionViewDelegateFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect) as! [UICollectionViewLayoutAttributes]
let offset = collectionView!.contentOffset ?? CGPoint.zero
let minY = -sectionInset.top
if (offset.y >= minY) {
let setOffset = fabs(170 - minY)
let extraOffset = fabs(offset.y - minY)
if offset.y <= 170 {
for attributes in layoutAttributes {
if let elementKind = attributes.representedElementKind {
if elementKind == UICollectionElementKindSectionHeader {
var frame = attributes.frame
frame.size.height = max(minY, headerReferenceSize.height - (extraOffset * 1.25))
frame.origin.y = frame.origin.y + (extraOffset * 1.25)
attributes.frame = frame
}
}
}
} else {
for attributes in layoutAttributes {
if let elementKind = attributes.representedElementKind {
if elementKind == UICollectionElementKindSectionHeader {
var frame = attributes.frame
frame.size.height = max(minY, headerReferenceSize.height - (setOffset * 1.25))
frame.origin.y = frame.origin.y + (setOffset * 1.25)
attributes.frame = frame
}
}
}
}
}
return layoutAttributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Here is a gif showing the behavior. Notice how it starts out fine and begins to flicker. Also fast scrolling has an undesired effect.
Any suggestions?
I don't think your issue is related to the layout code. I copied and tried using your CustomLayout in a simple sample app with no obvious flickering issues.
Other things to try:
Make sure your collectionView(viewForSupplementaryElementOfKind:at:) function properly reuses the header view, using collectionView.dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:)). Creating a new header view each time could cause substantial delays.
Do you have any complex drawing code in the cell itself?
Worst case, you could try profiling the app using Instruments Time Profiler to see what operations are taking up the most CPU cycles (assuming dropped frames are your issue).
Looks like that your collection view moves the header to the back.
Try insert this in code where you're changing frame of the header:
collectionView.bringSubview(toFront: elementKind)

SWIFT UITableview set autoheight according to contents - XCode - iOS

Automatically adjust the height of UITableView according to Contents Dynamically.
I tried the following solution but didn't work for me:
dispatch_async(dispatch_get_main_queue()) {
//This code will run in the main thread:
CGRect frame = self.tableView.frame;
frame.size.height = self.tableView.contentSize.height;
self.tableView.frame = frame;
}
SOURCE
I was having the same issue. i have solved it by simply passing the UITableView content height to UITabelView frame Height.
func UITableView_Auto_Height()
{
if(self.UITableView.contentSize.height < self.UITableView.frame.height){
var frame: CGRect = self.UITableView.frame;
frame.size.height = self.UITableView.contentSize.height;
self.UITableView.frame = frame;
}
}
Call the above function in viewDidAppear function of your viewController.
override func viewDidAppear(animated: Bool) {
UITableView_Auto_Height();
}
BAD::if(self.UITableView.contentSize.height < self.UITableView.frame.height){
GOOD::if(self.UITableView.contentSize.height > self.UITableView.frame.height){

UICollectionViewCell scale cell size while scrolling

I'm trying to create a UICollectionView where the UICollectionViewCell is getting scaled down when "leaving" the visible area at the top or bottom. And getting scaled up to normal size while "entering" the visible area.
I've been trying some scale/animation code in:
scrollViewDidScroll()
, but I can't seem to get it right.
My complete function looks like this:
func scrollViewDidScroll(scrollView: UIScrollView) {
var arr = colView.indexPathsForVisibleItems()
for indexPath in arr{
var cell = colView.cellForItemAtIndexPath(indexPath as! NSIndexPath)!
var pos = colView.convertRect(cell.frame, toView: self.view)
if pos.origin.y < 50 && pos.origin.y >= 0{
cell.hidden = false
UIView.animateWithDuration(0.5, animations: { () -> Void in
cell.transform = CGAffineTransformMakeScale(0.02 * pos.origin.y, 0.02 * pos.origin.y)
})
}else if pos.origin.y == 50{
UIView.animateWithDuration(0.5, animations: { () -> Void in
cell.transform = CGAffineTransformMakeScale(1, 1)
})
}
}
}
Is this in some way the right approach, or is there another better way?
Not a complete solution, but a few remarks/pointers:
You should not mess with the collection view cells directly in this way, but rather have a custom UICollectionViewLayout subclass that modifies the UICollectionViewLayoutAttributes to include the desired transform and invalidating the layout whenever necessary.
Doing if pos.origin.y == 50 is definitely not a good idea, because the scrolling might not pass by all values (that is, it might jump from 45 to 53). So, use >= and include some other way if you want to ensure that your animation is only executed once at the "boundary" (for example, store the last position or a flag).

Resources