Why does a UIScrollView instance reset its contentSize (in my case to the frame, and screen, width) by the time the delegate scrollViewDidEndZooming:(UIScrollView *) aScrollView withView:(UIView *) aView atScale:(float) aScale gets called?
What should I do about this? For example, do I really need to remind the instance of its contentSize, perhaps in the layoutSubviews method?
I believe the delegate method you are looking for is
(void)scrollViewDidZoom:(UIScrollView *)scrollView
It tells the delegate that the scroll view’s zoom factor changed.
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html
Related
There is a button at the bottom of my view controller. When the user scrolls down the button has to be attached to the scrollview at certain height.
I need to attach a button to the scrollview, immediately when the contentOffset.y reaches a particular value. -(void) scrollviewDidScroll doesn't help me as there might be a jump in contentOffset when the user is scrolling fast. Any leads on this are helpful.
Also, whenever I add a subview to the scrollview, -(void) viewDidLayoutSubviews is called. Which in turn sets the contentOffset to {0,0}. How can I achieve the functionality I need?
I needed to do the same thing with a UITableView and for me using scrollViewDidScroll worked.
I created a view called staticBar and added it as a subview of the tableView, but I had to rearrange the tableview subviews for it to appear in the right place. I don't have my code in front of me, but in -scrollViewDidScroll: it looked something like this:
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
CGFloat staticBarAdjustedY = _staticBarY - scrollView.contentOffset.y;
CGFloat scrollViewYFloor = scrollView.frame.size.height - _staticBar.frame.size.height;
// This way maximum Y the view can have is at the base of the scrollView
CGFloat newY = MIN( staticBarAdjustedY, scrollViewYFloor);
_staticBar.frame = (CGRect){ { _staticBar.frame.origin.x, newY}, _staticBar.frame.size}
}
I will check my code later today and add more details here.
Also, you said the scrollviewDidScroll has jumps in contentOffset, but it's worth mentioning that these jumps are the same that the scrollView uses to scroll its own view. So it's not like you are "losing" frames on this delegate method.
Hope it helps.
PS: So, here is the rest of my code.
//I place my custom view as a subview of the tableView below it's last subview
//The last subview is for scroll indicators.
WTButtonsBar *buttonBar = [[WTButtonsBar alloc] init];
[self.tableView insertSubview:buttonBar belowSubview:self.tableView.subviews.lastObject];
In scrollViewDidScroll:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//In my app I needed my view to stick to the top of the screen
//thats why I use MAX here
//self.buttonsBarOriginalY is the view's position in the scrollView when it isn't attached to the top.
CGFloat newY = MAX(scrollView.contentOffset.y, self.buttonsBarOriginalY)
[_buttonsBar setFrame:(CGRect){{0, newY}, _buttonsBar.frame.size}];
}
I am trying to intercept the locationInView from the UIScrollView.panGestureRecognizer as the scroll is happening.
I was able to get the initial touch coordinates (when the scroll begins) implementing the following code inside the delegate method
#pragma mark UIScrollViewDelegate
- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {
CGPoint point = [scrollView.panGestureRecognizer locationInView:scrollView];
NSLog(#"%#", NSStringFromCGPoint(point));}
Is there a way to get the coordinates continuously as the scroll is happening? I understand that the scrollView.panGestureRecognizer is a UIPanGestureRecognizer #property of the UIScrollView object, so there should be a panGestureDetected #selector somewhere.
Thx in advance.... e
Add the UIScrollViewDelegate method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
And in there you can access the scrollView while it is scrolling. If you are not using a view that subclasses UIScrollView, be sure to explicitly implement the UIScrollViewDelegate. I assume that you should not have to since the delegate method in your question is being called but I just thought I would point that out.
Be sure you really want to use the locationInView: method because it will return the point of the gesture and not the contentOffset of the UIScrollView. In other words, the gesture point may be outside of the desired range, where the contentOffset is in direct relation to the UIScrollView object.
EDIT:
Just as an FYI, the method will be called a lot, so make sure you are not doing any heavy processing every time the method is called.
I have a UIView with a CATiledLayer visible within a UIScrollView.
At certain times I need to relocate the window contents relative to the scroll view origin.
I need to change the contentOffset property of the scroll view but, when I redraw, the contents are the same.
The sequence is
scrollView.contentOffset = newoffset ;
[contentView setNeedsDisplay]
This creates a jerky effect as the scrollView shifts.
Is there any way to freeze the visible area of the scroll view until the drawing is complete?
Essentially, I'd like to
[FREEZE DISPLAY]
scrollView.contentOffset = newoffset ; // But have no visible change
[contentView setNeedsDisplay]
[UNFREEZE DISPLAY AFTER a SHORT DELAY]
You should be doing that in your delegate methods of UIScrollView. It has a very specific set of methods that you can use to time your update appropriately.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
Between these 3 methods, I'm sure that one of these is going to make that call with the timing that you're looking for.
Last thing, watch out for how many times your calling the setNeedsDisplay method when utilizing these delegates. setNeedsDisplay isn't cheap on memory if you're calling it every millisecond!
Cheers
I have a UIScrollView that scrolls horizontally. I have about 10 views lined up horizontally in that scrollview. I want to find the view that contains the center point of the scrollview's frame (not contentView.frame) as it scrolls. I am thinking to use the - (void)scrollViewDidScroll:(UIScrollView *)scrollView UIScrollView delegate method, but I am not sure how to find this.
Thanks!
You can get the start-point of a frame of a view (myChildView) in the reference of another view (scrollView) using the following code:
CGPoint origin = [myChildView convertPoint:CGPointMake(myChildView.bounds.origin.x, myChildView.bounds.origin.y) toView:[self scrollView]];
Use the scrollViewDidScroll: delegate method to pick your views and perform the calculations there.
I've got a a few UIScrollView on a page. You can scroll them independently or lock them together and scroll them as one. The problem occurs when they are locked.
I use UIScrollViewDelegate and scrollViewDidScroll: to track movement. I query the contentOffset of the UIScrollView which changed and then reflect change to other scroll views by setting their contentOffset property to match.
Great.... except I noticed a lot of extra calls. Programmatically changing the contentOffset of my scroll views triggers the delegate method scrollViewDidScroll: to be called. I've tried using setContentOffset:animated: instead, but I'm still getting the trigger on the delegate.
How can I modify my contentOffsets programmatically to not trigger scrollViewDidScroll:?
Implementation notes....
Each UIScrollView is part of a custom UIView which uses delegate pattern to call back to the presenting UIViewController subclass that handles coordinating the various contentOffset values.
It is possible to change the content offset of a UIScrollView without triggering the delegate callback scrollViewDidScroll:, by setting the bounds of the UIScrollView with the origin set to the desired content offset.
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = desiredContentOffset;
scrollView.bounds = scrollBounds;
Try
id scrollDelegate = scrollView.delegate;
scrollView.delegate = nil;
scrollView.contentOffset = point;
scrollView.delegate = scrollDelegate;
Worked for me.
What about using existing properties of UIScrollView?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating) {
/// The content offset was changed programmatically.
/// Your code goes here.
}
}
Another approach is to add some logic in your scrollViewDidScroll delegate to determine whether or not the change in content offset was triggered programatically or by the user's touch.
Add an 'isManualScroll' boolean variable to your class.
Set its initial value to false.
In scrollViewWillBeginDragging set it to true.
In your scrollViewDidScroll check to see that is it true and only respond if it is.
In scrollViewDidEndDecelerating set it to false.
In scrollViewWillEndDragging add logic to set it to false if the velocity is 0 (as scrollViewDidEndDecelerating won't be called in this case).
Simplifying #Tark's answer, you can position the scrollview without firing scrollViewDidScroll in one line like this:
scrollView.bounds.origin = CGPoint(x:0, y:100); // whatever values you'd like
This is not a direct answer to the question, but if you are getting what appear to be spurious such messages, it can ALSO be because you are changing the bounds. I am using some Apple sample code with a "tilePages" method that removes and adds subview to a scrollview. This infrequently results in additional scrollViewDidScroll: messages called immediately, so you get into a recursion which you for sure didn't expect. In my case I got a nasty impossible to find crash.
What I ended up doing was queuing the call on the main queue:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(scrollView == yourScrollView) {
// dispatch fixes some recursive call to scrollViewDidScroll in tilePages (related to removeFromSuperView)
// The reason can be found here: http://stackoverflow.com/questions/9418311
dispatch_async(dispatch_get_main_queue(), ^{ [self tilePages]; });
}
}