I have two scrollview, one scrollview inside another and I want to scroll my main scrollview content first after that only I want to scroll subview scroll
CGFloat scrollOffset = texscrl.contentOffset.y;
if (scrollOffset == 0)
{
//This condition will be true when scrollview will reach to bottom
self.ArtistScroll.scrollEnabled=YES;
texscrl.scrollEnabled=YES;
}else
{
self.ArtistScroll.scrollEnabled=NO;
texscrl.scrollEnabled=YES;
}
Here, am using content offset for this.. so can anyone help me?
You can achieve the affect you describe by using a third scrollview to actually handle the touch gestures and manually set the contentOffset of the other scrollviews.
Here is how to achieve this for vertically scrolling content, which is I think what you are describing. In the code, outerScrollView is the main scrollview, innerScrollView is the sub-scrollview that is contained by the outer scrollview, and trackingScrollView is the third scrollview that only handles the touches - it has no content.
Create the three scrollviews such that trackingScrollView exactly covers outerScrollView. (I assume you will do this in XIB or storyboard so there is no code.)
Once your scrollviews have content (and whenever their contentSize or bounds change) you should set the contentSize of trackingScrollView:
self.trackingScrollView.contentSize = CGSizeMake(self.trackingScrollView.bounds.size.width,
self.outerScrollView.contentSize.height + self.innerScrollView.contentSize.height - self.innerScrollView.bounds.height);
This makes the content height be that of outerScrollView plus the scrollable distance of innerScrollView, allowing trackingScrollView to scroll over the total travel distance of both scrollviews.
Delegate for trackingScrollView and implement scrollViewDidScroll:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.trackingScrollView) {
CGFloat const offsetY = self.trackingScrollView.contentOffset.y;
// Calculate the maximum outer scroll offset
CGFloat const maxOuterOffsetY = self.outerScrollView.contentSize.height - self.outerScrollView.bounds.height;
// Calculate the maximum inner scroll offset
CGFloat const maxInnerOffsetY = self.innerScrollView.contentSize.height - self.innerScrollView.bounds.height;
if (offsetY < maxOuterOffsetY) {
// Scroll is within the outer scroll area or the top bounce zone
self.outerScrollView.contentOffset = CGPointMake(0, offsetY);
self.innerScrollView.contentOffset = CGPointZero;
} else if (offsetY < maxOuterOffsetY + maxInnerOffsetY) {
// Scroll is within the inner scroll area
self.outerScrollView.contentOffset = CGPointMake(0, maxOuterOffsetY);
self.innerScrollView.contentOffset = CGPointMake(0, offsetY - maxOuterOffsetY);
} else {
// Scroll is within the bottom bounce zone
self.outerScrollView.contentOffset = CGPointMake(0, offsetY - maxInnerOffsetY);
self.innerScrollView.contentOffset = CGPointMake(0, maxInnerOffsetY);
}
} else {
// Handle other scrollviews as required
}
}
Effectively we divide the tracking scroll area into two parts. The top part controls the scrolling within the outer scrollview and the bottom part controls the inner scrollview.
Assuming bounce is turned on (which generally it should be) we also need to handle scrolling outside the scroll area. We want the bounce to show on the outer scrollview so the top bounce is handled implicitly by the first conditional. However, the bottom bounce has to be handled explicitly by the third conditional, otherwise we would see the bounce on the inner scrollview.
To be clear, in this solution the two content scrollviews (outerScrollView and innerScrollView) do not receive any touches at all; all input is going to trackingScrollView. If you want input to subviews of the content scrollviews you will need a more advanced solution. I believe this can be done by putting trackingScrollView behind outerScrollView, removing outerScrollView's gesture recognizers and replacing them with those from trackingScrollView. I have seen this technique presented in an old WWDC session on UIScrollView but I have not tried it myself.
Related
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
For example in Tweetbot's iPhone app. When you open up the app and new tweets come in, it will just be appended to the top of the UIScrollView and the current tweet you see did not get refreshed. How can I achieve the same thing effect?. Say I have a UIScrollView with a UIView added to the UIScrollView starting at origin 10,10.
I then downloaded a few items and I want to put it at 10,10.. so I basically need to shift this old item at 10,10 down right? If I do so then during that shifting user's will see it shifted, which is not the effect I want. Where as in Tweetbot's app it seems that nothing is being shifted around, it's just that you grow the area above the 10,10 and append new stuff's there.
How do I do this?
Basically I wanted to implement the insertRowAtIndexPath in a UIScrollView.
Will restate the question this way: how to add content to the top of a UIScrollView without moving the content that's already there (relative to it's current offset).
If this is the right question, the right answer is to do the add and shift down just as you suggested, but then scroll by the same height as added content, giving the illusion that the old content didn't move.
- (void)insertRowAtTop {
// not showing insert atIndexPath because I don't know how you have your subviews indexed
// inserting at the top, we can just shift everything down. you can extend this idea to
// the middle but it requires that you can translate from rows to y-offsets to views
// shift everything down
for (UIView *view in self.scrollView.subviews) {
if ([view isKindOfClass:[MyScrollViewRow self]]) {
MyScrollViewRow *row = (MyScrollViewRow *)view; // all this jazz so we don't pick up the scroll thumbs
row.frame = CGRectOffset(row.frame, 0.0, kROW_HEIGHT); // this is a lot easier if row height is constant
}
}
MyScrollViewRow *newRow = [[MyScrollViewRow alloc] initWithFrame:CGRectMake(0.0,0.0,320.0,kROW_HEIGHT)];
// init newRow
[self.scrollView addSubview:newRow];
// now for your question. scroll everything so the user perceives that the existing rows remained still
CGPoint currentOffset = self.scrollView.contentOffset
self.scrollView.contentOffset = CGPointMake(currentOffset.x, currentOffset.y + kROW_HEIGHT);
}
If you set the contentOffset without animating there wont be a visible scrolling animation. So if your new view is 200 points tall you can set the origin of the new view at (10,10) and the old view at (10,210) and set the contentOffset of the scroll view to (10,210) you should achieve the effect you intend. You'll also need to increase the contentSize of your scroll view to be big enough for all of the content it contains.
So I have a UIScrollView with the zooming functionality delegated to a view controller which of course overrides:
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView { ... etc ...
and everything works (it zooms and scrolls).
Then I took it up a notch an added two other UIScrollViews to the top level view whose purposes is to act as guides for a grid (think rows and columns in M$ Excel for example).
Therefore one of the scrollView's subview contains a row of UILabels (A, B, C, D...) and the other is identical except the UILabels are in a column (1, 2, 3, 4...). Picture a chess board if this helps.
Now I connected the vertical and horizontal scrolling of the main scrollView to the guide scroll views like so:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.scrollView) {
self.verticalGuideScrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y);
self.horizontalGuideScrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 0);
}
}
And everything works.
I connected the zooming functionality of the scrollView's like so:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (scrollView == self.scrollView) {
self.verticalGuideScrollView.zoomScale = scrollView.zoomScale;
self.horizontalGuideScrollView.zoomScale = scrollView.zoomScale;
}
}
and that also works fine EXCEPT I don't actually want it to enlarge the subviews (i.e the UILabels) in the guide views, I merely want the translation behaviour that comes from zooming without the scaling (enlarging or shrinking).
I can envision a solution by adding a UIPinchGestureRecognizerto the main scrollView and using that to translate all the subviews in the guide scroll views but perhaps someone here can come up with something simpler.
I have the UIScrollView with pagingEnabled set to YES, and programmatically scroll its content to bottom:
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.y = scrollView.contentSize.height - scrollView.frame.size.height;
[scrollView setContentOffset:contentOffset animated:YES];
it scrolls successfully, but after that, on single tap its content scrolls up to offset that it has just before it scrolls down. That happens only when I programmaticaly scroll scrollView's content to bottom and then tap. When I scroll to any other offset and then tap, nothing is happened.
That's definitely not what I'd like to get. How that should be fixed?
Much thanks in advance!
Timur.
This small hack prevents the UIScrollView from scrolling when tapped. Looks like this is happening when the scroll view has paging enabled.
In your UIScrollView delegate add this method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
scrollView.pagingEnabled = self.scrollView.contentOffset.x < (self.scrollView.contentSize.width - self.scrollView.frame.size.width);
}
This disables the paging when the scroll view reaches the right end in horizontal scrolling (my use case, you can adapt it to other directions easily).
I just figured out what causes this problem, and how to avoid it. If you having pagingEnabled set to YES on a scroll view, you must set the contentOffset to be a multiple of the scroll view's visible size (i.e. you should be on a paging boundary).
Concrete example:
If your scroll view was (say) 460 pixels high with a content area of 920, you would need to set the content offset to EITHER 0 or 460 if you want to avoid the "scroll to beginning on tap" problem.
As a bonus, the end result will probably look better since your scroll view will be aligned with the paging boundaries. :)
The following workaround did help (assume that one extends UIScrollView with a category, so 'self' refers to its instance):
-(BOOL) scrolledToBottom
{
return (self.contentSize.height <= self.frame.size.height) ||
(self.contentOffset.y == self.contentSize.height - self.frame.size.height);
}
Then, one should sometimes turn pagingEnabled off, just at the position where scroll view reaches its bottom. In the delegate (pagingEnabled is initialy on of course, since the problem occurs only when it is enabled):
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.pagingEnabled == YES)
{
if ([scrollView scrolledToBottom] == YES)
scrollView.pagingEnabled = NO;
}
else
{
if ([scrollView scrolledToBottom] == NO)
scrollView.pagingEnabled = YES;
}
}
This seems to be a bug:
UIScrollView doesn't remember the position
I have tested this on iOS 4.2 (Simulator) and the issue remains.
When scrolling a ScrollView I would suggest using
[scrollView scrollRectToVisible:CGRectMake(0,0,1,1) animated:YES];
Where the rect is the position you're after. (In this case the rect would be the top of the scrollview).
Changing the content offset is not the correct way of scrolling a scrollview.
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;
}