UIScrollView scroll detection - iphone

I have a UIScrollView in a UIViewController view that scrolls horizontally. How can I detect whether the scroll is at the left end or right end or somewhere in the middle?

You will probably need to look in to a scrollViewDelegate method such as below.
ObjC
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"Point: %#", NSStringFromCGPoint(scrollView.contentOffset));
}
Swift
override func scrollViewDidScroll(scrollView: UIScrollView) {
println(scrollView.contentOffset)
}
Have a look at the apple docs.
Also make sure to set your scrollview delegate your_scroll_view.delegate = self; and your view controller must conform to <UIScrollViewDelegate>

GameBit is correct here, but to elaborate -
The UIScrollView has a member variable contentOffset, that describes how many pixels from the origin the scrollview has scrolled. A positive value is a scroll to the right, negative is a scroll to the left.
Is your UIScrollView in Paged mode? if so this will help:
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

This works for me in horizontal UIScrollView
Conform to UIScrollViewDelegate
In your ViewController - yourScrollView.delegate = self
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
var currentPage = yourScrollView.contentOffset.x / yourScrollView.bounds.size.width;
}
Do not use scrollViewDidScroll as you need to wait until scrolling ends

Related

UITextView - disable vertical scrolling

How can I disable vertical scrolling in my UITextView? I want it to basically just scroll horizontally.
In some circumstances, when trying to clamp down on unwanted UITextView scrolling I have found it helpful to add something like the following to the UITextView delegate (this is a UIScrollView delegate method but, of course, UITextView inherits from UIScrollView). This might work for you.
- (void)scrollViewDidScroll:(id)scrollView
{
CGPoint origin = [scrollView contentOffset];
[scrollView setContentOffset:CGPointMake(origin.x, 0.0)];
}
What about the scrollEnabled property? Setting the scrollEnabled property to NO stops the user from scrolling (in both directions), but there are occasions where the system sends setContentOffset:animated: messages to a UITextView. The scrollEnabled property applies to both vertical and horizontal scrolling. Given your question, you might want to leave scrollEnabled as is.
You can change it from Xcode -
Solution for disabling vertical scrolling for Swift 4:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let origin: CGPoint = scrollView.contentOffset
scrollView.contentOffset = CGPoint(x: origin.x, y: 0.0)
}
If you have your custom textView subclass, you can override -gestureRecognizerShouldBegin to disable the scroll.
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
if (gestureRecognizer.view == self)
{
return NO;
}
else
{
return [super gestureRecognizerShouldBegin: gestureRecognizer];
}
}
}
why not just use a UITextField if you dont want vertical scrolling?
Just set the contentSize to the height of the view.
You'll use this:
CGSize scrollableSize = CGSizeMake(widthOfContent, heightOfView);
[myScrollView setContentSize:scrollableSize];
place your UITextView in a UIScrollView.
Set your UITextView.frame to a Size the complete Text fits in a Line and set the contenSize of the ScrollView to the size of your UITextView.frame.
Cheers
nettz

UIViews with UIPageControl

I'm a beginner when it comes to page control, and that's why I'm not sure 100% if my title agrees with what I want. I want to make a UIPageControl that when the user swipes on the screen, the view would switch over to another view and the UIPageController at the bottom of the screen would update itself. Also, to make my request even more confusing, I want a tab bar at the bottom of the screen that would stay put as the views change.
A great example of this is The Iconfactory's Ramp Champ:
http://img.slidetoplay.com/screenshots/ramp-champ_5.jpg
The bar at the bottom stays put while the rest of the items on the screen moves. What would be the easiest way to do this?
EDIT: I know I have to use a UISrollView, I just don't know how to go about implementing it...
I believe what you're looking for is actually a UIScrollView with pagingEnabled set to YES. You can leave the scrollview as a view above a regular UITabBar. You'll use a UIPageControl to get the little dots. You can update it programmatically when the UIScrollView scrolls to a page by implementing an appropriate delegate method of the scroll view, maybe -scrollViewDidScroll:.
Assume you have two ivars: scrollView and pageControl. When you know how many pages your scroll view will have, you can set the contentSize of scrollView. It should be a multiple of the scrollView's bounds. For example, if the number of pages is static you can hardcode it in your -viewDidLoad...
- (void)viewDidLoad {
// Any other code.
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.bounds.size.width * 3, scrollView.bounds.size.height); // 3 pages wide.
scrollView.delegate = self;
}
Then, to update your little dots...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.bounds.size.width;
NSInteger pageNumber = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = pageNumber;
}
You need to use a UIScrollView
Assuming you have a named ivar called scrollView
int amountOfFrames = 10;
scrollView.pagingEnabled = TRUE;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * amountOfFrames, scrollView.frame.size.height);
scrollView.delegate = self;
You will then need to implement the required delegate methods, so that you can update your page control
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
}
You need to place whatever content you want to be scrollable inside these scrollview, ideally lazyload into it, if the content you will displaying will require a lot of heap memory, use the scrollviewDidScroll to remove and add content at the required positions

UIScrollView disable scrolling while rotating on iPhone/iPad

I am using UIScrollView and an image in it as paging one image per page. I have a problem while rotating the iPhone
When I rotate the iPhone then scrollViewDidScroll (Scroll view delegate method) is calling.
Due to this, my paging is disturbed and the page number changes.
What is the solution?
Raphaël's answer is an excellent description of the problem, and a neat fix. I had the exact same problem and ended up fixing with a scrollingLocked flag that I set to YES (locked) before the rotation starts, and NO (unlocked) when it ends. Perhaps slightly less hacky than temporarily changing the contentSize:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
duration:(NSTimeInterval)duration
{
self.photoViewer.scrollingLocked = YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromOrientation
{
self.photoViewer.scrollingLocked = NO;
}
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
if (self.scrollingLocked)
{
return;
}
/* do normal scrollViewDidScroll: stuff */
}
I found a strange undocumented behavior when rotating a paged UIScrollView.
When the scrollview is positioned at the last page and the user changes the orientation, the OS scrolls the UIScrollView a few pixels back to compensate for the difference between height and width.
Basically I received the following calls for any page.
willRotateToInterfaceOrientation:duration
willAnimateRotationToInterfaceOrientation:duration:
didRotateFromInterfaceOrientation:
And for the last page:
willRotateToInterfaceOrientation:duration
scrollViewDidScroll:
willAnimateRotationToInterfaceOrientation:duration:
didRotateFromInterfaceOrientation:
That messed up with my pages too. The problem is that in willRotate, the bounds have not been updated by the OS yet, and in willAnimate you have the new bounds and can compute the new size, but it's too late...
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
CGSize tempSize = [self.pagingScrollView contentSize];
NSUInteger padding = abs(pagingScrollView.frame.size.width - pagingScrollView.frame.size.height);
tempSize.width += padding;
[self.pagingScrollView setContentSize:tempSize];
[...]
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration{
CGSize newSize = ... // Compute new content size based on new orientation
[self.pagingScrollView setContentSize:newSize];
}
This is just a workaround, but I spent countless hours on this issue and could not find an elegant solution.
Swift 4 solution:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
lui.l("apply previousTraitCollection: \(previousTraitCollection)")
canScroll = true
}
You can try this method for Swift:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
// Execute before rotation
}) { _ in
//Execute after rotation
}
}
My task is to allow scrolling the landscape. The design is for portait. I came up with an idea to add a ScrollView to components, or in "Embed in Scroll View" in Interface Builder. I have expected it will work, but no. I am using Xcode 4.4, iOS 5.1, (office project need support for 4.2 too), but the problem is the same.
In Stack Overflow question iPhone SDK: UIScrollView does not scroll there is one row which solve a problem.
Other try is in Stack Overflow question iOS - UIScrollView is not working (it doesn't scroll at all - the image stays fixed), and this helped me, combined with other, so here is my portait-to-scrollable landscape code:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromOrientation
{
if( UIInterfaceOrientationIsPortrait( [[UIApplication sharedApplication] statusBarOrientation] ) ){
scrollView.contentSize = portaitScrollSize;
}
else{//statusbar is Landscape
scrollView.contentSize = landscapeScrollSize;
}
}
The scrollView in bound to an iVar view in Interface Builder. portaitScrollSize and landscapeScrollSize are private variables. They are initialized and doesn't change.
In my.h file:
IBOutlet UIScrollView *scrollView;
In my.m file:
CGSize portaitScrollSize, landscapeScrollSize;
...
portaitScrollSize = CGSizeMake(320,440);
landscapeScrollSize = CGSizeMake(480,480);
I hope it will help somebody to add a rotating + scroll feature to a portait design.
Don't forget to allow portait+landscape on the top component:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return TRUE;
}
In addition to Raphaël Mor's answer. If you are switching from portrait to landscape, the contentsize and the page structure will brake. Therefore, in order to maintain the current page structure just add extra content size to width:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[self.scrollView setContentSize:CGSizeMake(self.scrollView.contentSize.width + 400, self.scrollView.contentSize.height)];
}
And make sure you set the contentsize and offset again after the orientation changed:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self.scrollView setContentSize:CGSizeMake(self.scrollView.bounds.size.width *3, self.scrollView.bounds.size.height)];
[self.scrollView setContentOffset:CGPointMake(self.scrollView.bounds.size.width * self.pageControl.currentPage, 0) animated:NO];
}

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;
}

How to page-scroll on a uiscrollview?

If I've got a uiscrollview which is five pages wide, what code would tell me what page I am on, when I scroll to a new page? Also, what code would scroll to a specific page?
Thanks!
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
int currentPage = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = currentPage;
}
I have the scrollview frame width as the page width. If you are using something else as page width, make respective changes to get this to work
for scrolling to current page - if I understand it right, you have an option to enter page number and choose to scroll to that page:
Just the code above, do it reverse order, and solve for contentOffset.X
once you have the x value, create a frame with that x in it, and scrollToRect: animated: will do the work.
Check out the PageControl example from Apple:
http://developer.apple.com/iphone/library/samplecode/PageControl/Listings/ReadMe_txt.html
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat scroller_x = [scrollView contentOffset].x;
CGFloat scroller_y = [scrollView contentOffset].y;
NSLog(#"%f, %f",scroller_x,scroller_y);
}
Once this delegate method fires (on scroll) check it against each views frame.origin.x and frame.origin.y The view's frame that matches will be the view that you scrolled to.
[scrollView setContentOffset:<#(CGPoint)#>];
use this method to scroll to a specific view.frame.origin. So suppose you have an action to go to a specific view, you can use [scrollView setContentOffset:view.frame.origin] to get there.
(assumed paging is enabled)