I have a makeshift custom "actionbar" at the top of my view, and a scrollView beneath it.
I was wondering if there is anyway to change the text displayed in the bar at the top depending on what is currently visible at my current position in my scrollView.
ANSWER
Make sure you have made your ViewController a ScrollView delegate, it should look like this:
#interface ViewController : UIViewController <UIScrollViewDelegate>
Once you have declared your ScrollView inside your .h file and synthesised it in the .m file make sure you tell it that it's delegate is itself:
ScrollView.delegate = self;
Then use the following method to detect where you are in your current scrollview
- (void) scrollViewDidScroll:(UIScrollView *)scroll {
if(scroll.contentOffset.y > /*Your Y coordinate*/){
// What you want to do when it reaches your y coordinate.
}
Implement UIScrollViewDelegate, and use the scrollViewDidScroll function. This will notify you every time the scrollView is scrolled. You can get the current location of the scrollView, and update your text accordingly.
More help on Apple Documentation
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];
}
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.
In my iPad app I have an UITableView. Table's frame size is less than screen size, so to make search functionality look nice I have to adjust searchResultTableView's frame to fit my TableView. I'm doing it in my UISearchDisplayDelegate's -searchDisplayController:willShowSearchResultsTableView: method.
Everything works fine except dimming view. When I'm starting search dimming view's width is equal to screen width:
When I start entering search string or clear textfield my searchResultsTableView resizes properly and everything works as it should:
I tried to change searchResultsTableView frame inside -searchDisplayControllerWillBeginSearch: method using this line
controller.searchResultsTableView.frame = myFrame;
but it doesn't work as well. Any suggestions besides implementing my own search display controller?
I also needed to change the frame of the dimming view but for a different reason. In my case I created a UISearchDisplayController and UISearchBar programmatically in a regular UIViewController not a UITableViewController. I was also using MFSideMenu which added to the complexity of the problem. What ended up happening was the dimming view was in the correct position initially but the next time the search was cleared the dimming view shifted leftwards and upwards by exactly half of it's size. Given the UISearchDisplayController you can find the dimming view like so.
for(UIView * v in controller.searchContentsController.view.subviews)
{
if([v isMemberOfClass:[UIControl class]])
{
v.frame = newFrame; //This frame should account for the UISearchBar
}
}
To handle for the initial incorrect frame you should change it in this delegate method:
- (void) searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView;
To handle for an incorrect frame on any subsequent clears you should change it in this delegate method:
- (void) searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView;
Note: this solution runs through the subviews of the searchContentsController which is one of the reasons I used isMemberOfClass instead of isKindOfClass (UIButton is a subclass of UIControl). Further discrimination would be required if you added a UIControl instance into your view (you could use tags to help determine which ones are yours).
How can I programmatically scroll a UIWebView in Objective-C (iOS)?
The only solution I can think of is to put it inside a UIScrollView and programmatically scroll that, however this presents the problem that the web view is often the wrong size and therefore the bottom of the page is cut off when scrolling. I have no idea how to programatically change the size of a web view to fit its content either... so far I have:
UIView *webDocView = webView.subviews.lastObject;
webView.frame = CGRectMake(webView.frame.origin.x, webView.frame.origin.y, webView.frame.size.width, webDocView.frame.size.height);
But for some reason this will not work.
Should I persue the solution of using a ScrollView... or is there a better way to do this?
UIWebView doesn't have any properties that specify its position on the document it is rendering. You likely need to do this through Javascript, if you want the animated feel of it take a look at this. http://www.learningjquery.com/2007/09/animated-scrolling-with-jquery-12
Apple says: "You should not embed UIWebView objects in UIScrollView object. If you do so, unexpected behavior can result because touch events for the two objects can be mixed up and wrongly handled." This probably has a lot to do with instances of both UIWebView and UIScrollView being scrollable and the interaction of two scrolling requests is too much.
Here is a simple solution that I've tested and it works:
Add the following in viewDidLoad to add a scrollView on top of our webView:
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.delegate = self;
[self.webView addSubview:self.scrollView];
When the page loads, we adjust the frame and contentSize of the scrollView
-(void) webViewDidFinishLoad:(UIWebView *)webView {
self.scrollView.frame = webView.bounds;
self.scrollView.contentSize = webView.scrollView.contentSize;
}
Now you can change offset of the web view.scrollView by setting self.webView.scrollView.contentOffset to whatever CGPoint that you want programatically.
Remember that, by doing this, touch events are no longer passed to the webiview. If you want the web view to still respond to the user dragging, then also do this:
#pragma mark UIScrollView Delegate
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
self.webView.scrollView.contentOffset = scrollView.contentOffset;
}