Resize UICollectionView while scrolling - iphone

My collection view begins life short, at the bottom of it's view controller's view. When the user scrolls up a little bit, I'd like the collection to fill the view. I'd like it to shrink again when the user pulls down into the bounce area.
Here's the code that I'd like to make work (and I think should work):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.collectionView) {
CGFloat offsetY = self.collectionView.contentOffset.y;
if (offsetY > 20.0) {
[self setCollectionViewExpanded:YES];
} else if (offsetY < -10.0) {
[self setCollectionViewExpanded:NO];
}
}
}
- (void)setCollectionViewExpanded:(BOOL)expanded {
// avoid starting a do-nothing animation
BOOL currentlyExpanded = self.collectionView.frame.origin.y == 0.0;
if (expanded == currentlyExpanded) return;
// note: the collection view is the uppermost subview of the vc's view
// expanding to view bounds makes it cover every other view
CGRect newFrame = (expanded)? self.view.bounds : CGRectMake(0.0, 240.0, 320.0, self.view.bounds.size.height-240.0);
[UIView animateWithDuration:0.3 animations:^{
self.collectionView.frame = newFrame;
}];
}
But after the expand happens, the cells remain in the same position relative to the parent view (still near the bottom) and the collection view stops responding to panning touches and sends no further didScroll notification.
I can get the content repositioned properly by doing a reloadData in an animation completion block, but the scrolling touches stop working either way.
A while back, I tried something like this with a table view (or another kind of scroll view) and ran into a similar hangup. Much obliged if anyone has this solved...

Can you describe your view hierarchy a bit better? Is the UICollectionView a subview of the UIScrollView?
Is self.view a UIScrollView?
In that case, the bounds origin is the scrollview's contentOffset (not 0,0). That could give your UICollectionView an unexpected frame.
If the UICollectionView is a subview of the scrollview, the frame origin is the origin of the view in the scrollview-space. So don't change the frame's origin (just its size) and set the contentOffset of the scrollView to make it visible.

Related

How to control two UIScrollViews in the same time with one touch?

Anyone has an idea how to control two scroll views while one in on the top of the other. One scrolls, the other one scrolls too. Same with zoom, gesture recognisers, etc ... Kinda like passing replica of the touches received by the first view onto the the one underneath. Subclass of the top scroll view has got a weak reference to the "dependant" scroll view underneath. Very important is to get the delegate methods working for both scrollviews as there is a lot of logic in these ...
use the scrollView Delegate methood
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if([scrollView isEqual:scrollViewA]) {
CGPoint offset = scrollViewB.contentOffset;
offset.y = scrollViewA.contentOffset.y;
[scrollViewB setContentOffset:offset];
} else {
CGPoint offset = scrollViewA.contentOffset;
offset.y = scrollViewB.contentOffset.y;
[scrollViewA setContentOffset:offset];
}
}
or simply in the same method for both horizontal an vertical scrolling
if([scrollView isEqual:scrollViewA]) {
scrollViewB.contentOffset = scrollViewA.contentOffset;
}
and viceVersa

Resize a UITableView on scroll

I've got a VC with a table view. When an event occurs, I want to drop in a UIView from the top of the main view. When the user scrolls the table view, I want to re-layout the view so that the dropped in view "scrolls away". I figured I'd do this by moving the upperView's frame and resizing the table view (both in relation to the scroll offset). I've got it almost working as follows:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
if (contentOffsetY > 0) {
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
}
NSLog(#"%f", self.upperView.frame.origin.y);
}
The upper view origin starts at 0,0.
The problem is, after a little dragging back and forth, I lose the top few pixels of that upper view. It can't seem to get it's origin y back to zero. The logging reads negative values, and only gets to -1, with the most careful dragging. Has anybody done something like this? Much obliged if you can help.
It sounds like you always scroll the table view to the top when you show the drop-in view. Assuming that's the case, there is a better way to do this.
UITableView inherits the contentInset property from UIScrollView. The contentInset property defines a border on each edge of the scroll view. Each border has its own thickness, which is zero by default. These borders just affect how far the scroll view is willing to let the user scroll the content - they don't hide the content! If you set the top inset larger than zero, and give the scroll view a subview with a negative Y origin, that subview can be visible in the border, and will scroll with the rest of the scroll view's content.
So we'll set the table view's top inset to the height of the drop-in view, and add the drop-in view as a subview of the table view with its origin set to the negative of its height. This will make it fit perfectly on the screen above the first row of the table view, and it will scroll with the table view. When we detect that the drop-in view has been scrolled fully off-screen, we can just remove it from the table view and set the table view's top inset back to zero.
We'll need an instance variable that tracks the current state of the drop-in view:
typedef enum {
DropInViewStateHidden,
DropInViewStateAppearing,
DropInViewStateVisible
} DropInViewState;
#implementation ViewController {
DropInViewState _dropInViewState;
}
In my test project, I just used a button to trigger the drop-in view. Here's the action:
- (IBAction)dropIn {
if (_dropInViewState != DropInViewStateHidden)
return;
CGRect frame = self.dropInView.frame;
frame.origin.y = -frame.size.height;
self.dropInView.frame = frame;
[self.tableView addSubview:self.dropInView];
self.tableView.contentInset = UIEdgeInsetsMake(frame.size.height, 0, 0, 0);
[self.tableView setContentOffset:frame.origin animated:YES];
_dropInViewState = DropInViewStateAppearing;
}
When the table view scrolls, we check the state of the drop-in view. If it is in the “visible” state and has been scrolled off-screen, we hide it. There's a tricky bit because when we make the drop-in view visible, and scroll it onto the screen, we can receive scrollViewDidScroll: messages that would make us think the drop-in view has been hidden. That's why we start out in the DropInViewStateAppearing state, and transition to the DropInViewVisible state when we know the view has appeared.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
switch (_dropInViewState) {
case DropInViewStateHidden:
break;
case DropInViewStateVisible:
if (scrollView.contentOffset.y >= 0) {
// dropInView has been scrolled off-screen
self.tableView.contentInset = UIEdgeInsetsZero;
[self.dropInView removeFromSuperview];
_dropInViewState = DropInViewStateHidden;
break;
}
case DropInViewStateAppearing:
// When I first add dropInView to tableView and tell tableView
// to scroll to reveal dropInView, I may get a bunch of
// scrollViewDidScroll: messages with contentOffset.y >= 0.
// I don't want those messages to hide dropInView, so I sit in
// DropInViewStateAppearing until contentOffset.y goes negative,
// which means at least part of dropInView is actually on-screen.
if (scrollView.contentOffset.y < 0)
_dropInViewState = DropInViewStateVisible;
break;
}
}
Figured this out: The UITableView doesn't thoroughly message didScroll during the bounce. This is why I was missing a few pixels. Resizing during the bounce makes the bounce get mixed up and stop. This fix on my code above allows the bounce to work (by moving, not resizing the table) and makes sure the upper view is correctly placed during the bounce (when contentOffset.y <= 0).
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
if (contentOffsetY > 0) {
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
} else {
self.upperView.frame = CGRectMake(0, 0, 320, upperHeight);
scrollView.frame = CGRectMake(0.0, upperHeight, 320, scrollView.frame.size.height);
}
[super scrollViewDidScroll:scrollView];
}

UIView resize and translate animation does not animate subviews correctly

I am using a UIView animation to resize and translate a view containing multiple subviews. The animation for the parent view happens perfectly; however, the subviews exhibit strange behaviour. When the animation begins, the subviews are immediately resized and then moved to their final position.
For example, if the duration and delay of the animation was five-seconds, as soon as the animation was called, the subviews would move to the desired end-of-animation values. After five-seconds, the superview would be resized and translated to the desired.
My code for the animation is:
[UIView animateWithDuration:0.5 animations:^{
if (UIDeviceOrientationIsLandscape(self.interfaceOrientation)) {
self.leftPaneView.frame = leftPaneLandscapeFrame;
self.rightPaneContainerView.frame = rightPaneLandscapeFrame;
}
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {
CGFloat offset = 300;
self.leftPaneView.frame = CGRectOffset(leftPanePortraitFrame, -offset, 0);
self.rightPaneContainerView.frame = rightPanePortraitFrame;
}
}];
Any ideas?
Note: rightPaneContainerView contains the view of a UIViewController that is a child of the view controller that calls this animation.
I managed to solve the problem. The content mode for some of the views was set to Left. When the animation started, the views would jump the left, and then be animated to the desired end-of-animation value.
An amateur mistake. Thanks everyone who took a look.

How to know the present visible view's tag in iphone

i have an scroll view and i am adding few views to that
when the user scrolls how to get the current visible view's tag.
then i can add some thing to that view...
its just like getting the indexpathrow in table view..
how to do that..?
Thanks
You basically want to check if the frame of the subviews inside the UIScrollView intersect the scrollview's frame (if you only want to determine partial visibility), or if the frame is contained in the other frame (if you want to determine full visibility).
However, in order to check if the subview's frame intersects and/or is contained in the scrollview's frame you need to translate it from the local coords inside the scrollview to the global coordinates outside the scrollview.
That is probably pretty confusing, so here is some code. This will loop through all the subviews of a scrollview and print out whether it is fully visibile or partially visible:
for (UIView *subview in scrollView)
{
CGRect globalRect = CGRectOffset(subview.frame, -scrollView.contentOffset.x, -scrollView.contentOffset.y);
CGRect scrollViewBounds = CGRectMake(0.0f, 0.0f, scrollView.bounds.size.width, scrollView.bounds.size.height);
if (CGRectContainsRect(scrollViewBounds, globalRect)) {
NSLog(#"FULLY VISIBLE");
} else if (CGRectIntersectsRect(scrollViewBounds, globalRect)) {
NSLog(#"PARTIALLY VISIBLE");
}
}
You could put this in a UIScrollViewDelegate method to do these checks while the user is scrolling the content around.

Limiting the scrollable area in UIScrollView

I have a UIScrollView that is scrolling a fairly large UIView.
At certain times I want to limit the area the user can scroll around in. For example, I may only want to allow them to view the bottom quarter of the view.
I am able to limit the area by overriding scrollViewDidScroll and then calling setContentOffset if the view has scrolled too far. But this way I can't get it bounce back as smoothly as the UIScrollView can naturally do when scrolling beyond the bounds of the UIView.
Is there a better way to limit the scrollable area in a UIScrollView?
I would change the contentSize property of the scroll view to the size of the area you want the user to be able to scroll around in and adjust the frame.origin of the subview such the upper left boundary you want appears at (0, 0) relative to the scroll view. For example, if your view is 800 points tall and you want to show the bottom quarter, set the height of contentSize to 200 and set the y component of view.frame.origin to -600.
I've found something that works for me. It let's you scroll to point 0,0 but no further:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x <= -1) {
[scrollView setScrollEnabled:NO];
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
[scrollView setScrollEnabled:YES];
}
}
You could do the same for top, bottom or right (x or y)
a small improvement on Yoko's answer in Swift 4 will be
override func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 600 {
let anim = UIViewPropertyAnimator(duration: 1, dampingRatio: 0.5) {
scrollView.isScrollEnabled = false
scrollView.setContentOffset(CGPoint(x: 0, y: 600), animated: false)
scrollView.isScrollEnabled = true
}
anim.startAnimation()
}
}
which will make the scrollview animate really similar to what its supposed to do. The slower drag when you are in the "bounce" area will not work and animation duration has to depend on the distance (not constant like here) if you want to be exact. You can also try to do this logic in scrollViewDidScroll and see how it differs. The key thing is that setContentOffset(_:,animated:) has to be with animated: false so that the UIViewPropertyAnimator's block can capture it and animate it
Another approach is to override the UIScrollView's method:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event.
Returning YES will allow the user to scroll. Returning NO will not.
NOTE: This will disable all touches to any views imbedded inside the UIScrollView that pointInside returns NO to. Useful if the area you don't want to scroll from doesn't have any interaction.
This example only allows the UIScrollView to scroll when the user is scrolling over a UITableView. (A UITableView and two UIViews are imbedded inside the UIScrollView)
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *subview in self.subviews) {
if ([subview pointInside:[self convertPoint:point toView:subview] withEvent:event] && ![subview isKindOfClass:[UITableView class]]) {
return NO;
}
}
return YES;
}