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.
I've done a custom UITableViewCell` with a front view and a back view for doing something like mailbox.
I've added the gestureRecognizer to scroll the front view so
self.slideRecognizer =
[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(slide:)];
[self.slideRecognizer setDelegate:self];
[self addGestureRecognizer:self.slideRecognizer];
The problem is that I cant scroll the UITableView down unless I tap outside the UITableViewCell (for example on UITableView header) and go down.
There should be something to add or edit in my method
- (void)slide:(UIPanGestureRecognizer *)panRecognizer
It's true?
THAT'S THE SOLUTION FOR ME ____________________________________________________________________________
Based on Simon's link, I've added this code in my tableViewController and in my custom cell
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
The problem was that assigning the custom pan gestureRecognizer I've invalidating the default one.
I would take a look at this question which seems quite similar. It sounds as though the gesture is only being captured by that cell and not passed through.
Tell ScrollView to Scroll after other pan gesture
I'm not sure if this is exactly the same issue as yours however. You should post more code.
On a side note, personally as a developer and a user I find scrolling elements inside scrolling elements to be every irritating to use. From a usability perspective I would consider a redesign. Thats only my opinion however.
So this is related to this question I asked not too long ago and I'm trying to figure out if the solution I came up with is feasible, I would try it but I'm not really sure how to start
Load a TableViewController on an animated UIView
So what I wanted to do is to have two different results when a animated uiview pops up.
I have two buttons. One button will have a uitable that shows a label and 3 icons, while the other will show one label and a different icon.
I'm using storyboards (since that's the only way I know how to do it) to connect a "Custom UitableCell" to the table.
I dragged two uitableviews and force them to be hidden from the view when viewdidload happens. So I place a prototype cell on each uitableview specific to what objects I need displayed when it gets loaded.
Now I add the uitabledelegate and datasource on the viewcontroller file.
Now my issue is, if I do this on the viewcontroller, can it only communicate to one uitable and not two? (eg. didselectrow, numberofrowsinsection, cellforrowatindexpath, etc) Or can I do and "if else" to figure out which uitable view it is communicating (or loading info) with?
This is the tutorial I followed for th uiview animation
Thoughts?
Yes, your view controller can be the data source and delegate of two uitableview or more.
Make your two UITableView properties of the view controller. Then in your delegate and data source methods, you can check the tableView parameter passed in each method, for example:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if (tableView == self.firstTableView) {
return 1; //for the firstTableView
} else {
return 2; //this will be for the secondTableView
}
}
Do the same for your other data source and delegate methods.
At the moment, I trigger a method on 'Did End On Exit' in my app (I'm aware that this may not be the greatest way of doing it but I'm very new to Objective C and Xcode for that matter and I'm simply doing what feels comfortable to me).
This method resigns the firstResponder from the current text field and applies it to a later text field.
The problem I'm facing is that the keyboard covers the next text field so that the use has no idea where the focus is and therefore what they are required to type.
How do I get it so that my keyboard shifts down and actually shows the text box that is currently active? Making something the firstResponder simply doesn't do what I want it to, unless there's part of the implementation I'm missing.
Here's my simple method:
- (IBAction)firstNameNext:(id)sender {
[firstNameTextField resignFirstResponder];
[surnameTextField becomeFirstResponder];
}
Any advice would be super.
Add UIScrollView in your main view then all contents as subview to UIScrollView
Now when specific UITextField needs to be able to visible in view use its delegate like this:
Note: add UITextFieldDelegate in .h file like this
#interface yourViewController : UIViewController<UITextFieldDelegate>
Also bind with File's Owner
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
{
if(textField == yourSpecficTextField) //one u want move upwards
{
yourScrollView.contentOffset = CGPointMake(0,200); //required offset
}
... //provide contentOffSet those who needed
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
yourScrollView.contentOffset = CGPointMake(0,0); //make UIScrollView as it was before
}
If you have keyboard input fields that will be covered by the virtual keyboard, then you need to move those fields out from under the virtual keyboard.
The normal way to do this is to have the controller's view be a scrollable view like UIScrollView. Moving Content That Is Located Under the Keyboard gives a very robust way of adjusting your scroll view and ensuring the required field shows.
A picture's worth a thousand words...
For a bit more background, I have a UITableView leveraging iOS 5's allowsMultipleSelectionDuringEditing set to YES. This results in the empty and filled edit controls being shown on the left of the cell any time the cell is in edit mode. This behavior is exactly what I want. I just want to change the appearance of these check marks.
I know it would be possible to write custom selection logic and basically roll my own version (like this and this), but that's what I want to avoid. The system is already in place, and I want to re-use as much of it as possible.
This is the closest I've come. It's simple and it works, while reusing almost all of the pre-baked system. It's also a giant hack however, and relies on exploiting the undocumented view hierarchy of UITableViewCell after a little runtime introspection.
In a nutshell, this simply hides the view normally responsible for showing the checkmark, allowing me to add my own view that can be shown in its place. I can then manipulate this stand-in view when the cell's selection or editing state changes...
To prevent the standard checkmark from appearing, all that's needed is a custom -layoutSubviews implementation. It's called, per the documentation, after both -willTransitionToState: and -setEditing:animated:, ensuring the state is always valid when either isSelected or isEditing changes.
- (void)layoutSubviews
{
[super layoutSubviews];
// Find the offending view, and quietly bury it...
for (UIView* subview in [self subviews])
{
// As determined by NSLogging every subview's class, and guessing which was the one I wanted
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"])
{
[subview setHidden:YES];
}
}
if ([self isEditing])
{
// Show the custom view however you want.
// The value of [self isSelected] will be useful...
}
else
{
// Hide the custom view.
}
}
I would still welcome a solution that's a bit more... kosher.