I want to change the height of my UITableView based on the scrolled content. Right now I do it by getting the scrollViewDidScroll event and then getting scrolled value and then change the height. Here is my code (for simplification I omitted the irrelevant code):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float currentOffset = scrollView.contentOffset.y
double delta = currentOffset - storedOffset;
if(delta != 0)
{
delta = abs(delta);
CGRect newListFrame = myTableView.frame;
float newListHeight = newListFrame.size.height + delta;
newListFrame.size.height = newListHeight;
myTableView.frame = newListFrame;
}
storedOffset = currentOffset;
}
But this approach is wrong because with this approach my UITableView's content is scrolled only a little bit and that's not what I want. I just want to get the value of that list that would be scrolled without actually scrolling it. Is there any way to do that? I thing I could get raw finger moved event but can I get it on UITableVIew? Can I do something like this using a UITableView method?
Related
I'd like to implement a "zoom" effect on a paging UIScrollView that I've created, but I am having a lot of difficulty. My goal is that as a user begins to scroll to the next page, the current page zooms out to become a little bit smaller. As the next page comes into view, it zooms in until it becomes its full size. The closest thing I could find to an example was this...
https://www.pinterest.com/pin/147141112804210631/
Can anyone give me some pointers on how to accomplish this? I've been banging my head against a wall for the last 3 days on this.
I would recommend using the scrollView.contentOffset.y of your paginated UIScrollView to keep track of the scroll and to use that value to animate the transform of your views inside the UIScrollView.
So add your paginated scrollview and make self as delegate.
paginatedScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, [[self view] bounds].size.height-paginatedScrollViewYOffset)];
[self.view addSubview:paginatedScrollView];
paginatedScrollView.pagingEnabled = YES;
[paginatedScrollView setShowsVerticalScrollIndicator:NO];
[paginatedScrollView setShowsHorizontalScrollIndicator:NO];
[paginatedScrollView setAlwaysBounceHorizontal:NO];
[paginatedScrollView setAlwaysBounceVertical:YES];
paginatedScrollView.backgroundColor = [UIColor clearColor];
paginatedScrollView.contentSize = CGSizeMake([[self view] bounds].size.width, [[self view] bounds].size.height*2); //this must be the appropriate size depending of the number of pages you want to scroll
paginatedScrollView.delegate = self;
Then use the delegate method scrollViewDidScroll to keep track of the scrollView.contentOffset.y
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"Scroll Content Offset Y: %f",scrollView.contentOffset.y);
//use here scrollView.contentOffset.y as multiplier with view.transform = CGAffineTransformMakeScale(0,0) or with view.frame to animate the zoom effect
}
Use this Code scrollview its zoom in when scroll next page, the code is given below,
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
GridCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectCell" forIndexPath:indexPath];
cell.myscrollview.minimumZoomScale = 5.0;
cell.myscrollview.zoomScale = 5.0;
cell.myscrollview.contentSize = cell.contentView.bounds.size;
return cell;
}
if you change the zoom scale value its automatically zoom in or zoom out to be showed when scroll next or previous page.
hope its helpful.
I actually just posted an answer to a very similar question, where somebody tried to achieve this effect using a UICollectionView. The link to my answer is here: https://stackoverflow.com/a/36710965/3723434
Relevant piece of code I will post here:
So another approach would be to to set a CGAffineTransformMakeScale( , ) in the UIScrollViewDidScroll where you dynamically update the pages' size based on their distance from the center of the screen.
For every page, calculate the distance of its center to the center of yourScrollView
The center of yourScrollView can be found using this nifty method: CGPoint point = [self.view convertPoint:yourScrollView.center toView:*yourScrollView];
Now set up a rule, that if the page's center is further than x away, the size of the page is for example the 'normal size', call it 1. and the closer it gets to the center, the closer it gets to twice the normal size, 2.
then you can use the following if/else idea:
if (distance > x) {
page.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} else if (distance <= x) {
float scale = MIN(distance/x) * 2.0f;
page.transform = CGAffineTransformMakeScale(scale, scale);
}
What happens is that the page's size will exactly follow your touch. Let me know if you have any more questions as I'm writing most of this out of the top of my head).
I've done some work on stylized app guide page before.
For Me, I would use CADisplayLink to track the contentOffset.x of the scrollView, associate the value with your animation process. Don't put your views on the scrollView, put them on an overlay view of this scrollView.
This solution follows the philosophy: Fake it before you make it.
Based on CADisplayLink and physics simulation of UIScrollView, you will get smooth animation. Believe me.
What you really want isn't a UIScrollView, it's a UICollectionView with a custom layout. UICollectionViewLayoutAttributes has a transform property that you can set.
Say for example, in layoutAttributesForElementsInRect::
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElementsInRect(rect) else {
return nil
}
return attributes.map { attribute -> UICollectionViewLayoutAttributes in
if attribute.frame.origin.y < 0 {
let scale = -attribute.frame.origin.y / attribute.frame.height
attribute.transform = CGAffineTransformMakeScale(scale, scale)
}
return attribute
}
}
Here, you're filtering by if the element is on the screen (so non-visible elements won't be counted) and checking to see if the y offset is less than 0. If it is, you take the difference between the negated y value and the item's height and turn that into a proportional scale.
You can do it however you want, if you want the scale to be between 1 and 0.5 for example. I like this way of doing things over mucking around with a scroll view.
I've been working at this for a good 48 hours now and can't seem to solve it on my own. What I'm trying to achieve is when a UITableViewCell is scrolled from the bottom to the top of the screen it starts small, magnifies as it reaches the center portion of the screen, and then begins to reduce in size again as it scrolls up and off of the screen (and vice versa, top to bottom). I can kind of get this to work, but it seems as though the contentView is resizing, the actual cell height (of the UITableView) is not.
What I want:
Scrolling up or down smoothly adjusts the size of all the uitableviewcells based on its y position on the screen.
What appears to be happening (though I'm having trouble confirming):
Again, it's as if the cell height is not being adjusted, but the height of the contentView is.
I can achieve this by overriding the layoutVisibleCells method of the UITableView:
func layoutVisibleCells() {
let indexpaths = indexPathsForVisibleRows!
let totalVisibleCells = indexpaths.count - 1
if totalVisibleCells <= 0 { return }
for index in 0...totalVisibleCells {
let indexPath = indexpaths[index]
if let cell = cellForRowAtIndexPath(indexPath) {
var frame = cell.frame
if let superView = superview {
let point = convertPoint(frame.origin, toView:superView)
let pointScale = point.y / CGFloat(superView.bounds.size.height)
var height = frame.size.height + point.x;
if height < 150.0 {
height = 150.0;
}
if height > 200.0 {
height = 200.0;
}
frame.size.height = height;
NSLog("index %li: x: %f", index,frame.origin.x);
NSLog("point at index %li: %f, %f - percentage: %f", index,point.x,point.y, pointScale);
}
cell.frame = frame
}
}
}
I took the idea for making the adjustment in this method from COBezierTableView, which is written in Swift. The rest of my project is in Obj-C, so deciphering things was a bit challenging, as I'm still learning my way there.
Any insight on my current attempt, or suggestions for how to achieve this in another, completely different way, are totally welcome. Thanks in advance.
UPDATE
So I've actually found that the opposite is happening of what I suspected. The UITableViewCell frame IS being resized correctly. It's the cell's contentView that is not resizing fully. It does resize partially, but for some reason it does not resize to match the cell's frame. Not sure why ...
I wanna ask if its possible to change the alpha value of a navigation bar when the user is scrolling a tableview.
I have the algorithm, but i need something help to get changes on real time.
/* This is the offset at the bottom of the scroll view. */
CGFloat totalScroll = scrollView.contentSize.height - scrollView.bounds.size.height;
/* This is the current offset. */
CGFloat offset = - scrollView.contentOffset.y;
/* This is the percentage of the current offset / bottom offset. */
CGFloat percentage = offset / totalScroll;
/* When percentage = 0, the alpha should be 1 so we should flip the percentage. */
scrollView.alpha = (1.f - percentage);
It's probably too late, but for future reference, you could do something like this:
func scrollViewDidScroll(scrollView: UIScrollView) {
self.navigationController!.navigationBar.alpha = 1 - (self.tableView.contentOffset.y / (self.tableView.contentSize.height - self.tableView.frame.size.height));
}
On this delegate you have the values of contentOffset of scrollview or tableView, and you can observe the value of this property to make the behavior you desire.
Hope it helps!
I am implementing an infinite-scrolling calendar. My issue is that I would like to set the current month as the title in the navigation bar and it should update while scrolling - once you pass the section header view the title should update in the nav bar.
A possible solution would be to set the view title in the method called - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath so that, when I calculate a new section Header, it also updates the title. The problem with this is that the title changes when the new section is at the bottom of the page.
Is there a way to know the "current section" of UICollectionView once the user has scrolled to it? Or can you think of a way to improve my current solution?
To help the readers of this post, I posted my own sample code for this question at this GitHub repo.
I have been pondering an algorithm that would allow you to know when the user has scrolled past a section header in order to update the title, and after some experimentation I have figured out how to implement the desired behavior.
Essentially, every time the scroll position changes you need to know what section the user is on and update the title. You do this via scrollViewDidScroll on the UIScrollViewDelegate - remembering a collection view is a scroll view. Loop over all the headers and find the one that's closest to the current scroll position, without having a negative offset. To do that, I utilized a property that stores an array of each section header's position. When a header is created, I store its position in the array at the appropriate index. Once you've found the header that's closest to your scroll position (or the index location of said header), simply update the title in the navigation bar with the appropriate title.
In viewDidLoad, fill the array property with NSNull for each section you have:
self.sectionHeaderPositions = [[NSMutableArray alloc] init];
for (int x = 0; x < self.sectionTitles.count; x++) {
[self.sectionHeaderPositions addObject:[NSNull null]];
}
In collectionView:viewForSupplementaryElementOfKind:atIndexPath:, update the array with the position of the created header view:
NSNumber *position = [NSNumber numberWithFloat:headerView.frame.origin.y + headerView.frame.size.height];
[self.sectionHeaderPositions replaceObjectAtIndex:indexPath.section withObject:position];
In scrollViewDidScroll:, perform the calculations to determine which title is appropriate to display for that scroll position:
CGFloat currentScrollPosition = self.collectionView.contentOffset.y + self.collectionView.contentInset.top;
CGFloat smallestPositiveHeaderDifference = CGFLOAT_MAX;
int indexOfClosestHeader = NSNotFound;
//find the closest header to current scroll position (excluding headers that haven't been reached yet)
int index = 0;
for (NSNumber *position in self.sectionHeaderPositions) {
if (![position isEqual:[NSNull null]]) {
CGFloat floatPosition = position.floatValue;
CGFloat differenceBetweenScrollPositionAndHeaderPosition = currentScrollPosition - floatPosition;
if (differenceBetweenScrollPositionAndHeaderPosition >= 0 && differenceBetweenScrollPositionAndHeaderPosition <= smallestPositiveHeaderDifference) {
smallestPositiveHeaderDifference = differenceBetweenScrollPositionAndHeaderPosition;
indexOfClosestHeader = index;
}
}
index++;
}
if (indexOfClosestHeader != NSNotFound) {
self.currentTitle.text = self.sectionTitles[indexOfClosestHeader];
} else {
self.currentTitle.text = self.sectionTitles[0];
}
This will correctly update the title in the nav bar once the user scrolls past the header for a section. If they scroll back up it will update correctly as well. It also correctly sets the title when they haven't scrolled past the first section. It however doesn't handle rotation very well. It also won't work well if you have dynamic content, which may cause the stored positions of the header views to be incorrect. And if you support jumping to a specific section, the user jumps to a section whose previous section's section header hasn't been created yet, and that section isn't tall enough such that the section header is underneath the nav bar (the last section perhaps), the incorrect title will be displayed in the nav bar.
If anyone can improve upon this to make it more efficient or otherwise better please do and I'll update the answer accordingly.
change below line in method viewForSupplementaryElementOfKind :
self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section]];
to this:
if(![[self dateForFirstDayInSection:indexPath.section-1] isKindOfClass:[NSNull class]]){
self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section-1]];
}
Hope it will help you.
Yes, the problem is that footer and header are not exists in visibleCells collection. There is other way to detect scroll for section header/footer. Just add a control there and find the rect for it. Like this:
func scrollViewDidScroll(scrollView: UIScrollView) {
if(footerButton.tag == 301)
{
let frame : CGRect = footerButton.convertRect(footerButton.frame, fromView: self.view)
//some process for frame
}
}
No solution here is fulfilling, so I came up with my own that I want to share, fully. If you use this, you have to make some pixel adjustments, though.
extension MyViewControllerVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.myCollectionView {
let rect = CGRect(origin: self.myCollectionView.contentOffset, size: self.cvProductItems.bounds.size)
let cellOffsetX: CGFloat = 35 // adjust this
let cellOffsetAheadY: CGFloat = 45 // adjust this
let cellOffsetBehindY: CGFloat = 30 // adjust this
var point: CGPoint = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY + cellOffsetAheadY) // position of cell that is ahead
var indexPath = self.myCollectionView.indexPathForItem(at: point)
if indexPath?.section != nil { // reached next section
// do something with your section (indexPath!.section)
} else {
point = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY - cellOffsetBehindY) // position of cell that is behind
indexPath = self.myCollectionView.indexPathForItem(at: point)
if indexPath?.section != nil { // reached previous section
// do something with your section (indexPath!.section)
}
}
}
}
}
UICollectionView inherits UIScrollView, so we can just do self.myCollectionView.delegate = self in viewDidLoad() and implement the UIScrollViewDelegate for it.
In the scrollViewDidScroll callback we will first get the point of a cell below, adjust cellOffsetX and cellOffsetAheadY properly, so your section will be selected when the cell hits that point. You can also modify the CGPoint to get a different point from the visible rect, i.e for x you can also use rect.midX / rect.maxX and any custom offset.
An indexPath will be returned from indexPathForItem(at: GCPoint) when you hit a the cell with those coordinates.
When you scroll up, you might want to look ahead, possibly ahead your UICollectionReusableView header and footer, for this I also check the point with negative Y offset set in cellOffsetBehindY. This has lower priority.
So, this example will get the next section once you pass the header and the previous section once a cell of the previous section is about to get into view. You have to adjust it to fit your needs and you should store the value somewhere and only do your thing when then current section changes, because this callback will be called on every frame while scrolling.
Im attempting to replicate the resizing behaviour of a screen seen in the iOS app Rdio. The screen in question provides an overview of a selected album and contains a UIView on the top half and a UITableView on the bottom half. When the tableView is scrolled, it first resizes upwards to fill the screen, then begins to scroll through its content normally once the maximum height is reached.
After some searching I found this question: Dragging UITableView which is basically asking for the same thing, however its accepted method is the same as my initial thoughts & trial, which was to use a UIPanGestureRecognizer and resize the tableviews height according to the translation of the pan.
This does not provide the behaviour i'm looking for. Using this method only allows you to statically drag the tableviews height up or down and it has the added issue of the panGesture overriding that of the tableViews which then prevents scrolling through the content.
The resizing behaviour of the Rdio app functions and feels exactly like a UIScrollView, it has inertia. You can drag it all the way, flick it up or down, and it smoothly resizes. When the tableView has reached its full-size or original half-size, the remaining inertia is seemingly passed on the tableview causing the cells to scroll as they normally would for that amount. I know they must be manipulating UIScrollViews, I just can't figure out how.
As a final note, eventually I will be using AutoLayout on this screen so i'm wondering how that will potentially hinder or help this situation as well.
Update
This approach has gotten me closest to the behaviour i'm looking for so far.
Flicking the tableView upwards behaves exactly like I wanted it to (resize with inertia & continue scrolling when max height is reached), although with less sensitivity than i'd like. Flicking downwards however, provides no inertia and instantly stops.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect scrollViewFrame = scrollView.frame;
CGFloat scrollViewYOffset = scrollView.contentOffset.y;
if (scrollViewFrame.origin.y <= kTableViewMaxYOrigin || scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
{
scrollViewFrame.origin.y -= scrollViewYOffset;
if(scrollViewFrame.origin.y >= kTableViewMaxYOrigin && scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
{
scrollViewFrame.size.height += scrollViewYOffset;
scrollView.frame = scrollViewFrame;
scrollView.contentOffset = CGPointZero;
}
}
}
I made a version that uses autolayout instead.
It took me a while of trial and error to get this one right!
Please use the comments and ask me if the answer is unclear.
In viewDidLoad save the initial height of your layout constraint determining the lowest down you want the scrollview to be.
- (void)viewDidLoad
{
...
_initialTopLayoutConstraintHeight = self.topLayoutConstraint.constant;
...
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
BOOL leaveScrollAlone = self.topLayoutConstraint.constant == _initialTopLayoutConstraintHeight && scrollView.contentOffset.y <= 0;
if (leaveScrollAlone)
{
// This allows for bounce when swiping your finger downwards and reaching the stopping point
return;
}
// Do some capping of that layout constraint to keep it from going past the range you want it to be.
// In this case, I use self.topLayoutGuide.length so that my UICollectionView scales all the way until
// it hits the bottom of the navigation bar
CGFloat topLayoutConstraintLength = _initialTopLayoutConstraintHeight - scrollView.contentInset.top;
topLayoutConstraintLength = MAX(topLayoutConstraintLength, self.topLayoutGuide.length);
topLayoutConstraintLength = MIN(topLayoutConstraintLength, _initialTopLayoutConstraintHeight);
self.topLayoutConstraint.constant = topLayoutConstraintLength;
// Keep content seemingly still while the UICollectionView resizes
if (topLayoutConstraintLength > self.topLayoutGuide.length)
{
scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentInset.top + scrollView.contentOffset.y,
scrollView.contentInset.left,
scrollView.contentInset.bottom,
scrollView.contentInset.right);
scrollView.contentOffset = CGPointZero;
}
// This helps get rid of the extraneous contentInset.top we accumulated for keeping
// the content static while the UICollectionView resizes
if (scrollView.contentOffset.y < 0)
{
self.topLayoutConstraint.constant -= scrollView.contentOffset.y;
scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentInset.top + scrollView.contentOffset.y,
scrollView.contentInset.left,
scrollView.contentInset.bottom,
scrollView.contentInset.right);
}
// Prevents strange jittery artifacts
[self.view layoutIfNeeded];
}
It turns out the key component to getting the smooth inertial resizing in both directions was to update the scrollViews contentInset.top by its contentOffset.y.
I believe this makes sense in retrospect as if the content within is already at the top it cannot scroll anymore, hence the sudden stop rather than smooth scroll. At least thats my understanding.
Another key point was to make sure the cells only started scrolling once maximum or original height was achieved. This was done simply by setting the scrollViews contentOffset to CGPointZero each time the view resized until maximum or original height was reached.
Here is the - (void)scrollViewDidScroll:(UIScrollView *)scrollView method demonstrating how to achieve this effect.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect scrollViewFrame = scrollView.frame;
CGFloat scrollViewTopContentInset = scrollView.contentInset.top;
CGFloat scrollViewYOffset = scrollView.contentOffset.y;
if (scrollViewFrame.origin.y <= kTableViewMaxYOrigin || scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
{
scrollViewFrame.origin.y -= scrollViewYOffset;
if(scrollViewFrame.origin.y >= kTableViewMaxYOrigin && scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
{
scrollViewFrame.size.height += scrollViewYOffset;
scrollViewTopContentInset += scrollViewYOffset;
scrollView.frame = scrollViewFrame;
scrollView.contentInset = UIEdgeInsetsMake(scrollViewTopContentInset, 0, 0, 0);
scrollView.contentOffset = CGPointZero;
}
}
}
I haven't seen the app in question, but from your description... in your tableView's delegate method -scrollViewDidScroll:, set tableView.frame.origin.y to albumView.frame.height - tableView.contentOffset.y and change its height accordingly.
(If you're using autolayout, you'll have to change the constraints pertaining to the tableView's frame rather than the frame itself.)