Custom UIView widget with UIScrollView not scrolling - ios

I am trying to develop a new custom UIView (to allow for horizontal date selection). I want to do all the UI design in XIB files.
The custom UI view contains a scrollview and then two 'week' views. The idea is that as the scrolling occurs, I will move the two 'week' views in place and reconfigure them to the right dates to create an 'infinite' scroll for date selections.
I can load the UIView, which then loads scrollview and week views (all designed in a XIB).
My DatePickerView class, derived from the UIView class does an addSubview of the scroll view (which contains the two week views). The scroll view is 320 wide and the contentSize is set to 640 wide. UserInteraction is enabled. Horizonal Scrolling is enabled.
This all works and displays on the screen. The week views each contain 7 buttons. I can press them and they get the touch. However, the scrollview does not seem to want to scroll.
I set my custom view to be a UIScrollViewDelegate. No calls occur to scrollViewDidScroll.
For each of the week views, I have a 'container' view and then the buttons. I added the following to the container view (again derived from a UIView).
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
DDLogInfo(#"Began. Next Responder: %#", self.nextResponder);
[self.nextResponder touchesBegan:touches withEvent:event];
}
(and comparable ones for the other touch events, touchesMoved, touchesEnded, touchesCancelled),
I print out the nextResponder, which is the UIScrollView, so I know that I am sending the touch to the view, but I never see the scrollview want to scroll.
Is my method of passing the touchEvents up the responder chain correct?
Is there anything else I need to configure to get the scrolling to work?
Any help is appreciated.
Charlie

If I understand correctly, you want infinite scroll with just three pages of scroll view. I achieved it with similar effects in my calendar view project.
You can checkout from here DPCalendar
In a nutshell, I created a view like
#interface DPCalendarMonthlyView : UIScrollView
And initial it like this
self.showsHorizontalScrollIndicator = NO;
self.clipsToBounds = YES;
self.contentInset = UIEdgeInsetsZero;
self.pagingEnabled = YES;
self.delegate = self;
I create three views like this
[self.pagingViews addObject:[self singleMonthViewInFrame:self.bounds]];
[self.pagingViews addObject:[self singleMonthViewInFrame:CGRectMake(self.bounds.size.width, 0, self.bounds.size.width, self.bounds.size.height)]];
[self.pagingViews addObject:[self singleMonthViewInFrame:CGRectMake(self.bounds.size.width * 2, 0, self.bounds.size.width, self.bounds.size.height)]];
Then I set the content size and also scroll it to the middle
[self setContentSize:CGSizeMake(self.bounds.size.width * 3, self.bounds.size.height)];
[self scrollRectToVisible:((UIView *)[self.pagingViews objectAtIndex:1]).frame animated:NO];
In the scrollview delegate function, i need to do
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
//If scroll right
if(self.contentOffset.x > self.frame.size.width)
{
//do something if scroll right
} else if(self.contentOffset.x < self.frame.size.width)
{
//do something else if scroll left
} else {
return;
}
//scroll back to the middle
[self scrollRectToVisible:((UICollectionView *)[self.pagingViews objectAtIndex:1]).frame animated:NO];
}
Hopefully it is useful to you.

For those that follow down this path, I figured this out and it was a silly error. I forgot to turn off AutoLayout. I keep forgetting that Apple put autoLayout as an enable/disable option under the 'document'-level of a NIB (so I forget to look there).
Turned it off and it works as designed. Looks like autoLayout was causing the views to be rearranged to not need to be scrolled, or something equivalent.

Related

UIScrolllView scroll limit position

I have an UITableView inside an UIScrollView and I want that the user will be able to scroll just when he touches the tableView (I have a map in the background and I want that the user will be able to integrate with the map when the tableView doesn't covers all of it.
I've tried to set scrollViewHeightConstraint.constant to scrollView.contentOffset.y on -(void)scrollViewDidScroll:(UIScrollView *)scrollView but it didn't work, part of the map is still not touchable. Can anyone give me an advice what should I do? Thanks!
Screenshot: Screenshot
OK. I think I understand what you're trying to do. Here is the solution I have come up with. Let me know if this works for you or not.
First off, you need to set the contentInset of the table view in your view controller (or if it's possible in the storyboard, set it there. Not sure if it is though?). It's a simple one-liner in viewDidLoad or viewWillAppear:, like so:
self.tableView.contentInset = UIEdgeInsetsMake(400.f, 0.f, 0.f, 0.f);
The 400.f is just saying start the table view's content 400 points from the top. You can set it to whatever number you'd like or, if you know you want it 200 points from the bottom, do something like this:
self.tableView.contentInset = UIEdgeInsetsMake(self.view.bounds.size.height - 200.f, 0.f, 0.f, 0.f);
With your content situated OK, now you need to create a subclass of UITableView to use as your table view's class. This class should override one method: hitTest:withEvent:. You can set which class you're using for your table view in your storyboard (click on the table view, then go to the Identity Inspector), or you can just change it in your view controller if you're not using storyboards.
In the method you're overriding, you're checking to see if where the user has touched the screen is above the contentInset or not (which works for your design). For a more complex design, you'd need some more robust checking.
This should allow the MKMapView to intercept the scroll events from the table view (which is returning nil and disabling scrolling).
Here is the UITableView subclass (you can name it whatever you want):
EmbeddedTableView.h
#import <UIKit/UIKit.h>
#interface EmbeddedTableView : UITableView
#end
EmbeddedTableView.m
#import "EmbeddedTableView.h"
#implementation EmbeddedTableView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (point.y < 0.f) {
self.scrollEnabled = NO;
} else {
self.scrollEnabled = YES;
return [super hitTest:point withEvent:event];
}
return nil;
}
#end

Automatic adjustment of UIScrollView content offset with custom UIControl

When a UITextField is added to a UIScrollview the scroll view automatically adjusts its contentOffset so that the view will not be obscured by the keyboard.
I have a custom UIControl which also presents a keyboard when it becomes the first responder by assigning its inputView property. The same scrolling behavior does not work. Is there a way to configure a UIControl such that a scroll view will keep it visible when the keyboard is presented?
My guess is that it could be possible by overriding a property defined in one of the protocols UITextField and other classes which this behavior conform to. But these can be a bit of a maze. Also note, the issue here has nothing to do with the scroll view's contentInset property. The scroll view can scroll to show the custom control, it just doesn't do it automatically when the control becomes the first responder.
It looks like this is handled by an internal private method that Apple utilizes [UIFieldEditor scrollSelectionToVisible] as noted on this blog: http://sugarrushva.my03.com/712423-disable-uiscrollview-scrolling-when-uitextfield-becomes-first-responder.html
It appears to do this by stepping back up through the view hierarchy and if it finds a parent UIScrollView, it scrolls the view to bring the UITextField into visible view. You'll need to implement the scrolling manually on your custom control when it becomes first responder, or handle it by introspecting the parent views.
I was pointed in the right direction by #markflowers.
Based on that, here's what I've written into the control to get the desired behavior:
- (BOOL)becomeFirstResponder {
if ([super becomeFirstResponder]) {
[self scrollParentViewToFrame];
return YES;
}
return NO;
}
- (void)scrollParentViewToFrame {
UIScrollView *scrollView = self.parentScrollView;
CGRect frame = [scrollView convertRect:self.bounds fromView:self];
[self.parentScrollView scrollRectToVisible:frame animated:YES];
}
- (UIScrollView *)parentScrollView {
return (UIScrollView *) [self closestParentWithClass:[UIScrollView class]];
}
Note that the frame attribute is not used in case the control is not a direct descendant of the scroll view. Instead convert the bounds to the scroll view's coordinate space.
The scroll adjustment is also needs to be performed after [super becomeFirstResponder] is called for it to interact properly with keyboard notifications that are being used to adjust the insets of the scroll view.
I defined the method to search for the closest parent scroll view in a UIView category which made it easier to recursively search up the hierarchy.
- (UIView *)closestParentWithClass:(Class)class {
if ([self isKindOfClass:class]) {
return self;
}
// Recursively searches up the view hierarchy, returns nil if a view
// has no superview.
return [self.superview closestParentWithClass:class];
}

UITableView inside UIScrollView not receiving first tap after scrollling

Brief
I am having an issue with a UITableView inside a UIScrollView. When I scroll the external scrollView, the table does not receive the willSelect/didSelect event on the first touch, but it does on the second one. What is even more strange, the cell itself gets the touches and the highlighted state, even when the delegate does not.
Detailed explanation
My view hierarchy:
UIView
- UIScrollView (outerscroll)
- Some other views and buttons
- UITableView (tableView)
Inside the scroll view I have some extra views that get expanded/closed dynamically. The table view needs to get "fixed" on top, together with some other elements of the view, so that is why I created this layout, that allows me to easily move elements in a similar way than Apple recommends by the use of transformations when the scroll happens.
The table View is transformed with a translation effect when the outerscroll moves like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.outerScrollView) {
CGFloat tableOffset = scrollView.contentOffset.y - self.fixedHeaderFrame.origin.y;
if (tableOffset > 0) {
self.tableView.contentOffset = CGPointMake(0, tableOffset);
self.tableView.transform = CGAffineTransformMakeTranslation(0, tableOffset);
}
else {
self.tableView.contentOffset = CGPointMake(0, 0);
self.tableView.transform = CGAffineTransformIdentity;
}
// Other similar transformations are done here, but not involving the table
}
In my cell, if I implement these methods:
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
NSLog(#"selected");
}
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
NSLog(#"highlighted");
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
NSLog(#"touchesBegan");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
NSLog(#"touchesEnded");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
NSLog(#"touchesCancelled");
}
Y can see this output when fails (first tap):
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] highlighted
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] touchesBegan
2014-02-10 13:04:40.978 MyOrderApp[5588:70b] touchesEnded
And this one when works (second tap):
2014-02-10 13:05:30.359 MyOrderApp[5588:70b] highlighted
2014-02-10 13:05:30.360 MyOrderApp[5588:70b] touchesBegan
2014-02-10 13:05:30.487 MyOrderApp[5588:70b] touchesEnded
2014-02-10 13:05:30.498 MyOrderApp[5588:70b] expanded
No other frame change, animation or any other view interaction is done between the first and the second tap. Also, only when scrolling large amounts the bug appears, but with scrollings of just a few pixels everything keeps working as expected.
I experimented changing some properties as well, but with no luck. Some of the things I did:
Remove userInteractionEnabled from views other than the scroll and table
Add a call to setNeedsLayout on the table, scroll and main view when scrollViewDidScroll occurs.
Remove the transformations from the table (still happens)
I have seen some comments about the unexpected behaviour of embedding UITableViews inside UIScrollViews but I can not see such a warn in the official documentation by Apple, so I am expecting it to work.
The app is iOS7+ only.
Questions
Has anyone experienced similar issues? Why is this and how can I solve it? I think that I could be able to intercept the tap gesture on the cell and pass it with a custom delegate or similar, but I would like the table to receive the proper events and so my UITableViewDelegate receives it as expected.
Updates
I tried disabling cell reuse as suggested in a comment but it still happens in the same way.
leave the inner UITableView's scrollEnabled property set as YES. this lets the inner UITableView know to handle scroll-related touches on the UIScrollView correctly.
From Apple Documentation, you shouldn't embed a UITableViewinside a UIScrollView.
Important: You should not embed UIWebView or UITableView objects in
UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
Your problem is really related to what your UIScrollView does.
But if it's just to hide the tableview when needed (that was my case), you can just move the UITableView in its superview.
I wrote a small example here : https://github.com/rvirin/SoundCloud/
I ran into this same problem and figured out a solution!!
You need to set the delaysTouchesBegan to true on your scrollview so that the scrollview sends its failed scrolled-gesture (i.e. the tap) to its children.
var delaysTouchesBegan: Bool -
A Boolean value determining whether the receiver delays sending touches in a begin phase to its view.
When the value of the property is YES, the window suspends delivery of
touch objects in the UITouchPhaseBegan phase to the view. If the
gesture recognizer subsequently recognizes its gesture, these touch
objects are discarded. If the gesture recognizer, however, does not
recognize its gesture, the window delivers these objects to the view
in a touchesBegan:withEvent: message (and possibly a follow-up
touchesMoved:withEvent: message to inform it of the touches’ current
locations).
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//apple_ref/occ/instp/UIGestureRecognizer/delaysTouchesBegan
But there's a catch...it doesn't work if you do it directly on the scrollview!
// Does NOT work
self.myScrollview.delaysTouchesBegan = true
Apparently this is an iOS bug where setting this property doesn't work (thank you apple). However there's a simple workaround: set the property directly on the scrollview's pan gesture. Sure enough, this worked for me perfectly:
// This works!!
self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
It seems that your UiTableView doesn't recognize your tap. Did you try to use that :
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
return YES;
}
return NO;
}
Note from apple:
called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other. return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
Hope that will help.
Gesture recognizers won't work correctly for two embedded scroll views or subclasses.
Try a workaround:
Use transparent, custom, and overlaying everything in cell UIButton with proper tag, or subclass UIButton and add a index path property and overwrite each time in reused cell.
Add this button as a property to your custom cell.
Add target for desired UIControlEvent (one or more) that points to your UITableViewDelegate protocol adopting class.
Disable selecting in IB, and manually manage the selection from code.
This solution requires attention for cases of single/multi selection.
I've encountered a UITableView with scrollEnabled being NO within a UIScrollView in some legacy code. I have not been able to change the existing hierarchy easily nor enable scrolling, but come up with the the following workaround for the first tap problem:
#interface YourOwnTableView : UITableView
#end
#implementation YourOwnTableView
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
// Note that this is a hack and it can stop working at some point.
// Looks like a table view with scrollEnabled being NO does not handle cancellation cleanly,
// so let's repeat begin/end touch sequence here hoping it'll reset its own internal state properly
// but won't trigger cell selection (the touch passed is in its cancelled phase, perhaps there is a part
// of code inside which actually checks it)
[super touchesBegan:touches withEvent:event];
[super touchesEnded:touches withEvent:event];
}
#end
Again, this is just a workaround working in my specific case. Having a table view within a scroll view is still a wrong thing.
I would recommend to look for options like not letting your cell to be in highlighted state when you are actually scrolling the outer scroll view which is very easy to handle and is the recommended way. You can do this just by taking a boolean and toggling it in the below method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
The scrollview is trying to figure out whether the user's intention is to scroll or not, so it's delaying the initial touch on purpose. You can turn this off by setting delaysContentTouches to NO.
I have the same problem with nested UITableView and have found a work-around for this:
innerTableView.scrollEnabled = YES;
innerTableView.alwaysBounceVertical = NO;
You'll need to set the height of the inner table view to match with the total height of its cells so that it'll not scroll when user scrolling the outer view.
Hope this helps.
My mistake was implementing the delegate method:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
instead of the one I meant to implement:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Hence only being called on the second cell being tapped, because that was when the first cell would be de selected. Stupid mistake made with the help of autocomplete. Just a thought for those of you who may wander here not realizing you've made the same mistake too.
Drop a UIButton over your UITableViewCell and create the outlet as "btnRowSelect".
In your view controller put this code in cellForRowAtIndexPath
cell.btnRowSelect.tag = indexPath.row
cell.btnRowSelect.addTarget(self, action: Selector("rowSelect:"), forControlEvents: .TouchUpInside)
Add this function to your viewController as well-
func rowSelect (sender:UIButton) {
// "sendet.tag" give you the selected row
// do whatever you want to do in didSelectRowAtIndexPath
}
This function "rowSelect" will work as didSelectRowAtIndexPath where
you get the row"indexPath.row" as "sender.tag"
As other answers say you shouldn't put a tableview in a scrollview. A UITableView inherits from UIScrollView anyway so I guess that's where things get confusing. What I always do in this situation is:
1) Subclass UITableViewController and include a property UIView *headView.
2) In the parent UIViewController create all the top stuff in a container UIView
3) Initialise your custom UITableView and add the tableView's view to the view controller full size
[self.view addSubview: self.myTableView.view];
4) Set the headView to be your UIView gubbins
self.tableView.headView = myHeadViewGubbins.
5) In the tableViewController method
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger *)section;
Do:
if ( section == 0 ) {
return self.headView;
}
Now you have a table view with a bunch of other shizzle at the top.
Enjoy!
That it, if touch table view it will work properly. also with scroll view in same view controller also.
tableview.scrollEnabled = true;
I have the same issue, Then refer to "Nesting Scroll Views" as lxx said.
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html
An example of cross directional scrolling can be found in the Stocks application. The top view is a table view, but the bottom view is a horizontal scroll view configured using paging mode. While two of its three subviews are custom views, the third view (that contains the news articles) is a UITableView (a subclass of UIScrollView) that is a subview of the horizontal scroll view. After you scroll horizontally to the news view, you can then scroll its contents vertically.
It is work

Cross Directional UIScrollViews - Can I Modify the Scrolling Behaviour?

Here's how the scroll views work: One scroll view is paging enabled in the horizontal direction. Each 'page' of this scroll view contains a vertically scrolling UITableView. Without modification, this works OK, but not perfectly.
The behaviour that's not right: When the user scrolls up and down on the table view, but then wants to flick over to the next page quickly, the horizontal flick/swipe will not work initially - it will not work until the table view is stationary (even if the swipe is very clearly horizontal).
How it should work: If the swipe is clearly horizontal, I'd like the page to change even if the table view is still scrolling/bouncing, as this is what the user will expect too.
How can I change this behaviour - what's the easiest or best way?
NOTE For various reasons, a UIPageViewController as stated in some answers will not work. How can I do this with cross directional UIScrollViews (/one is a table view, but you get the idea)? I've been banging my head against a wall for hours - if you think you can do this then I'll more than happily award a bounty.
According to my understanding of the question, it is only while the tableView is scrolling we want to change the default behaviour. All the other behaviour will be the same.
SubClass UITableView. UITableViews are subClass of UIScrollViews. On the UITableView subClass implement one UIScrollView's UIGestureRecognizer's delegate method
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
//Edit 1
//return self.isDecelerating;
//return self.isDecelerating | self.bounces; //If we want to simultaneous gesture on bounce and scrolling
//Edit 2
return self.isDecelerating || self.contentOffset.y < 0 || self.contentOffset.y > MAX(0, self.contentSize.height - self.bounds.size.height); // #Jordan edited - we don't need to always enable simultaneous gesture for bounce enabled tableViews
}
As we only want to change the default gesture behaviour while the tableView is decelerating.
Now change all 'UITableView's class to your newly created tableViewSubClass and run the project, swipe should work while tableView is scrolling. :]
But the swipe looks a little too sensitive while tableView is scrolling. Let's make the swipe a little restrictive.
SubClass UIScrollView. On the UIScrollView subclass implement another UIGestureRecognizer's delegate method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
if (abs(velocity.y) * 2 < abs(velocity.x)) {
return YES;
}
}
return NO;
}
We want to make the "swipe is clearly horizontal". Above code only permits gesture begin if the gesture velocity on x axis is double than on y axis. [Feel free to increase the hard coded value "2" if your like. The higher the value the swipe needs to be more horizontal.]
Now change the `UiScrollView' class (which has multiple TableViews) to your ScrollViewSubClass. Run the project. :]
I've made a project on gitHub https://github.com/rishi420/SwipeWhileScroll
Although apple doesn't like this method too much:
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
I've found a great way to accomplish this.
This is a complete solution for the problem. In order to scroll the UIScrollView while your UITableView is scrolling you'll need to disable the interaction you have it.
- (void)viewDidLoad
{
[super viewDidLoad];
_myScrollView.contentSize = CGSizeMake(2000, 0);
data = [[NSMutableArray alloc]init];
for(int i=0;i<30;i++)
{
[data addObject:[NSString stringWithFormat:#"%d",i]];
}
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.view addGestureRecognizer:tap];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
[_myTableView setContentOffset:_myTableView.contentOffset animated:NO];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = YES;
}
To sum up the code above, if the UITableView is scrolling, set userInteractionEnabled to NO so the UIScrollView will detect the swipe. If the UITableView is scrolling and the user taps on the screen, userInteractionEnabled will be set to YES.
Instead of using UIScrollView as a container for these multiple table views, try using a UIPageViewController.
You can even integrate this into your existing view controller setup as a child view controller (directly replacing the UIScrollView).
In addition, you'll likely want to implement the required methods from UIPageViewControllerDataSource and possibly one or more of the methods from UIPageViewControllerDelegate.
Did you try the methods : directionalLockEnabled of both your table and scroll and set them up to horizontal for one and vertical for the other ?
Edit :
1)
What you want to do is very complicate since the touch wait some time (like 0.1s) to know what your movement will be. And if your table is moving, it will take your touch immediately whatever it is (because it's suppose to be reactive movement on it).
I don't see any other solution for you but to override touch movement from scratch to detect immediately the kind of mouvement you want (like if the movement will be horizontal) but it will be more than hard to do it good.
2)
Another solution I can advise you is to make your table have left and right margin, where you can touch the parent scroll (pages thing so) and then even if your table is scrolling, if you touch here, only your paging scroll will be touched. It's simpler, but could not fit with your design maybe...
Use UIPageViewController and in the -viewDidLoad method (or any other method what best suits your needs or design) get UIPageViewController's UIScrollView subview and assign a delegate to it. Keep in mind that, its delegate property won't be nil. So optionally, you can assign it to another reference, and then assign your object, which conforms to UIScrollViewDelegate, to it. For example:
id<UIScrollViewDelegate> originalPageScrollViewDelegate = ((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]).delegate;
[((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]) setDelegate:self];
So that you can implement UIScrollViewDelegate methods with ease. And your UIPageViewController will call your delegate's -scrollViewDidScroll: method.
By the way, you may be obliged to keep original delegate, and respond to delegate methods with that object. You can see an example implementation in ViewPagerController class on my UI control project here
I faced the same thing recently. My UIScrollview was on paging mode and every page contained a UITableView and like you described it worked but not as you'd expected it to work. This is how solved it.
First I disabled the scrolling of the UIScrollview
Then I added a UISwipeGestureRecognizer to the actual UITableView for left and right swipes.
The action for those swipes were:
[scroll setContentOffset:CGPointMake(currentPointX + 320, PointY) animated:YES];
//Or
[scroll setContentOffset:CGPointMake(currentPointX - 320 , PointY) animated:YES];
This works flawlessly, the only down side is that if the user drags his finger on the UITableVIew that will be considered as a swipe. He won't be able to see half of screen A and half of screen B on the same screen.
You could subclass your scroll view and your table views, and add this gesture recognizer delegate method to each of them...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I can't be sure this is exactly what you are after, but it may come close.

How to reset my UIScrollView's position after returning from a modal transition?

I have a simple view containing a long view with many buttons, with the whole thing being in a UIScrollView. The scroller works well, and I can scroll to the bottom and click a button. Every button triggers a modal seque to another view. That new view is then dismissed by user interaction, causing the original UIScrollView's view to load again.
Here's the problem: If I click on a button toward the top of the UIScrollView, I enter the modal segue, dismiss the new view, and return to the UIScrollView's view without a problem. But, if I click on one of the buttons toward the bottom of the UIScrollView, when I return seque out and then transition back, my scrolling is all messed up. I can only see the area beneath my scroller, and can't scroll back up to the top anymore!
I'm pretty sure there must be some way to reset the UIScrollView's starting and ending points upon ViewWillAppear, but I can't figure it out. Any help is appreciated!
Also, FYI, I simply added the UIScrollView through interface builder, and haven't implemented or synthesized it anywhere yet.
try this code:
- (void)viewWillAppear:(BOOL)animated
{
[yourscrollview setContentOffset:CGPointZero animated:YES];
}
Please note: the bug this question and answer is about appears to be fixed in iOS 7. The rest of this answer is only relevant to iOS 6 (and probably earlier).
The behaviour being exhibited here is a bug in the UIScrollView class. As noted by the OP, after returning from a modally presented UIViewController to a scene containing a UIScrollView, the UIScrollView takes whatever point it's currently scrolled to and starts behaving as though that is its origin. That means that if you'd scrolled down your scroll view before modally presenting another View Controller, you can't scroll back up upon returning to the scene with the scroll view.
The same thing happens when you remove the Scroll View from the view hierarchy and re-add it, even without changing its window.
You can work around this by setting the contentOffset of the scroll view back to {0,0} before it gets displayed again after dismissing the modal View Controller. If you actually want to preserve the point the user had scrolled to before they triggered the modal, then after the UIScrollView is redisplayed you can set the contentOffset back to whatever it was before you reset it.
Here's a UIScrollView subclass that fixes the bug without resetting the scroll view to the top whenever you return from a modal:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView {
CGPoint oldOffset;
}
-(void)willMoveToWindow:(UIWindow *)newWindow {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)didMoveToWindow {
self.contentOffset = oldOffset;
}
-(void)didMoveToSuperview {
self.contentOffset = oldOffset;
}
#end
If you'd rather do this in a UIViewController than in a UIScrollView subclass, change the content offset in the viewWillAppear: and viewDidAppear methods.
If you don't want to preserve where the user's scroll position when they return from a modal, and just want to scroll the UIScrollView back to the top, as the OP asked for, then all you need is the even simpler:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView
-(void)willMoveToWindow:(UIWindow *)newWindow {
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
self.contentOffset = CGPointMake(0,0);
}
#end
First, thanks for the approved answer above. Someone mentioned that it was no longer applicable but I have a scrolling view inside of table view cell and it needs to be reset when the cell is reused.
Here is the solution in Swift.
#IBOutlet var scrollView: UIScrollView!
// many lines of code later inside a function of some sort...
scrollView.setContentOffset(CGPointMake(0.0, 0.0), animated: false)
To solve this problem i use this code:
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.scrollview scrollRectToVisible:CGRectMake(0, 0, 1, 1)
animated:NO];
}
You can change the starting and ending points by calling scrollRectToVisible:animated:. But I'm not sure if this fixes your problem.
Use below code snippet to restore the scroll position for a UIScrollview
Declare "scrollPosition" variable as CGPoint type.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
//get the current offset
scrollPosition = scrollView.contentOffset;
//set current view to the beginning point
self.scrollView.contentOffset = CGPointZero;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
//retrieve the previous offset
self.scrollView.contentOffset = scrollPosition;
}

Resources