Avoid UIPageViewController "over-scrolling" - ios

I have 2 VCs on my UIPageViewController.
I want that if the user is on the 1st view, he won't be able to swipe to the right (just to the left - to the 2nd view), and if he's on the 2nd view he won't be able to swipe to the left (just to the right - to the 1st view).
I want that the user won't be able to scroll - not a black view (what happens when I return nil on: - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController)

The "black view" is likely to be the background of your main view, or the background colour of your UIPageViewController.
You can kill scrolling in one of the directions depending on the ViewController that the PageViewController is currently showing
first set your VC as the delegate for the PageViewController's inner scroll view. You can add a more sophisticated subviews traversal here, this is a simple version:
for (UIView *view in self.pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[(UIScrollView *)view setDelegate:self];
}
}
then add a property to your VC as this:
#property (nonatomic) CGPoint lastOffset;
and after that implement the following scroll view delegate methods
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
scrollView.scrollEnabled = YES;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.scrollEnabled = YES;
self.lastOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint nowOffset = scrollView.contentOffset;
NSLog(#"delta %f", self.lastOffset.x - nowOffset.x);
if ((self.lastOffset.x - nowOffset.x) < 0) {
//uncomment to prevent scroll to left
//scrollView.scrollEnabled = NO;
} else if ((self.lastOffset.x - nowOffset.x) > 0) {
//uncomment to prevent scroll to right
//scrollView.scrollEnabled = NO;
} else {
scrollView.scrollEnabled = YES;
}
}

Related

Scrolling a UIView along with UITableView scroll

I have a tableview in which UITableViewCell contains a textfield. When user tap on the textfield and keyboard appears, I scroll to the tapped in row after setting proper content inset.
Now, I have a UIView shown on top of my table view which I need to adjust accordingly when table scrolls.
To do this, I implemented scrollViewDidScroll: method as below. While this works in certain situations, it do not work or give random results in some situations. Am I doing something wrong here?
- (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.hollowView) {
CGPoint offset = iScrollView.contentOffset;
CGRect hollowViewFrame = self.hollowView.hollowFrame;
hollowViewFrame.origin.y += self.previousOffset - offset.y;
self.previousOffset = offset.y;
[self.hollowView resetToFrame:hollowViewFrame];
}
}
- (void)keyboardDidHide:(NSNotification *)iNotification {
self.previousOffset = 0.0;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
}
If you want to scroll the both view simultaneously then you should have to take both views with same size and in following method just set the scrollview content offset to them. It will surly work
enter code here - (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.scrollView) // Check for the scrolled view
{
// Set the Tableview offset directly
tableview.contentOffset = iScrollView.contentOffset
}
else {
//Set view scroll as per tableview scroll
view.contentOffset = iScrollView.contentOffset } }
//Note :- Make sure view should be scrollview type or if view is getting scared or shows black screen
Thanks..
Use this library:
https://github.com/hackiftekhar/IQKeyboardManager
It will handle the position of Views when tapped on a textfield.
So, this piece worked for me for anyone who is struggling with same situation:
- (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.hollowView) {
CGRect frame = [self.tableView rectForRowAtIndexPath:self.expandedCellIndexPath];
frame.origin.y -= self.tableView.contentOffset.y;
[self.hollowView resetToFrame:frame];
}
}
you can use this for scrolling table in ios.
-(void )keyboardWillShow:(NSNotification *)notification
{
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin =[keyboardInfovalueForKey:UIKeyboardFrameEndUserInfoKey];
keyBoardHeight = keyboardFrameBegin.CGRectValue.size.height;
vwSendBottomSpaceContraint.constant = keyBoardHeight
[self performSelector:#selector(scrollToBottomAnimated) withObject:nil afterDelay:0.1];
}
-(void)keyboardWillHide:(NSNotification*)notification
{
vwSendBottomSpaceContraint.constant = 0;
[self performSelector:#selector(scrollToBottomAnimated) withObject:nil afterDelay:0.1];
}
-(void)scrollToBottomAnimated
{
NSInteger rows = [tableview numberOfRowsInSection:0];
if(rows > 0)
{
[tableview scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottomanimated:YES];
}
}

Unable to scroll vertically UIScrollView/UITableView of ChildViewController if scrolling of UIScrollView of parent class is disabled forcefully

AIM: To achieve the scroll of parent upto a particular point. Freeze its scroll and let the child view scroll till the bottom. Again move up, let child view controller scroll, till it reaches its top then let the parent view scroll till it reaches top and page comes to original state.
I am adding any childviewcontroller like this:
- (void)switchCurrentControllerWith:(UIViewController*)viewController andViewHeight:(float)height{
//1. The current controller is going to be removed
[selectedViewController willMoveToParentViewController:nil];
// ((DRBaseViewController *)viewController).hideNavBar = YES;
//2. The new controller is a new child of the container
[self addChildViewController:viewController];
//3. Setup the new controller's frame depending on the animation you want to obtain
viewController.view.frame = containerRect;
//The transition automatically removes the old view from the superview and attaches the new controller's view as child of the
//container controller's view
[self transitionFromViewController:selectedViewController
toViewController:viewController
duration:0.1
options:UIViewAnimationOptionShowHideTransitionViews|UIViewAnimationOptionTransitionCrossDissolve
animations:^{
} completion:^(BOOL finished) {
//Remove the old view controller
[selectedViewController removeFromParentViewController];
//Set the new view controller as current
selectedViewController = viewController;
[selectedViewController didMoveToParentViewController:self];
[self.view bringSubviewToFront:navigationBar];
[self.scrollView setContentSize:CGSizeMake(320., CGRectGetMaxY(segmentedControl.frame) + height)];
}];
}
Now, there is a UIScrollView scaled to fit view height (504) in the parent view controller. I am doing following tasks in scrollview delegate methods for disabling the parent's scrollview and enabling the scroll of childviewcontroller's scrollview/tableview :
-(void)scrollViewDidScroll:(UIScrollView*)scrollView
{
if (scrollView.contentOffset.y >= self.segmentScrollView.frame.origin.y) {
scrollView.scrollEnabled = NO;
for (UIViewController *selectedViewController in containedControllers) {
for (UIView *view in selectedViewController.view.subviews) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView*)view;
tableView.scrollEnabled = YES;
}
else if ([view isKindOfClass:[UIScrollView class]]){
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.scrollEnabled = YES;
}
}
}
}
else{
for (UIViewController *selectedViewController in containedControllers) {
for (UIView *view in selectedViewController.view.subviews) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView*)view;
tableView.scrollEnabled = NO;
}
else if ([view isKindOfClass:[UIScrollView class]]){
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.scrollEnabled = NO;
}
}
}
}
if ([scrollView isAtBottom]) {
for (UIView *view in selectedViewController.view.subviews) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView*)view;
tableView.scrollEnabled = YES;
}
else if ([view isKindOfClass:[UIScrollView class]]){
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.scrollEnabled = YES;
}
}
}
}
Now my PROBLEM IS THE CHILDVIEW'S SCROLL/TABLEVIEW is not getting scrolled when the parent's scrollview reaches a fixed content offset and then the childview's scrollview have to be scrolled! What seems to be the problem here?
PS. I checked the childview's Scroll/TableView's frame and content Size and that is not an issue.
NOTE: I see the scrolling of my child view's table/scrollview has been disabled in one direction and is enabled in another!

iOS hiding navigation bar due to scroll amount

I've tried to implement such a table view that detects scroll amount and decides to show navigation bar or do not.
#interface HomeViewController () {
NSInteger scrollAmount;
bool navbarHidden = NO;
}
#implementation HomeViewController
#synthesize lastContentOffset = _lastContentOffset;
bool navbarHidden = NO;
- (void)awakeFromNib
{
[super awakeFromNib];
scrollAmount = 0;
distance = 50;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
UIEdgeInsets inset = scrollView.contentInset;
if (offset.y > self.lastContentOffset.y)
{
scrollAmount++;
}
else
{
scrollAmount--;
}
bool awayFromTop = offset.y > distance + inset.top;
if (awayFromTop && !navbarHidden) {
[[self navigationController] setNavigationBarHidden:YES animated:YES];
navbarHidden = YES;
} else if (!awayFromTop || (scrollAmount < -100)) {
[[self navigationController] setNavigationBarHidden:NO animated:YES];
navbarHidden = NO;
}
self.lastContentOffset = offset;
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
scrollAmount = 0;
}
Basically, scrollViewDidScroll counts scroll amount and if user scrolls upward, it is decrementing scrollAmount by minus 1.
And if offset is close enough to top of the screen (!awayFromTop) OR scroll amount is smaller than -100, it is expected to navigation bar is hid.
When I put a NSLog for scrollAmount program runs correct, it is hiding nav. bar when user aways from top or shows when approaches to top and scrollAmount is printed correctly.
But whenever scrollAmount reaches to -100 [[self navigationController] setNavigationBarHidden:NO animated:YES]; is not executed and somehow scrollViewDidScroll is called infinitely I mean program enters an infinite loop. scrollAmount is printed like -100,-101,-102...,-1005...
Then I've used below code:
if ([scrollView.panGestureRecognizer translationInView:self.view].y < heightOfScreen/-4.0f && !navbarHidden) {
[self.navigationController setNavigationBarHidden:YES animated:YES];
navbarHidden = YES;
} else if ([scrollView.panGestureRecognizer translationInView:self.view].y > heightOfScreen/4.0f && navbarHidden) {
[self.navigationController setNavigationBarHidden:NO animated:YES];
navbarHidden = NO;
}
Obviously [scrollView.panGestureRecognizer translationInView:self.view].y gives sth. similar to scrollAmount but it works perfect, now I wonder why my implementation has been failed. Any ideas appreciated.
The code looks right but if I were you, I would use
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
to track the scroll view scrolling, because scrollViewDidScroll generates some weird behavior in my app too. Maybe you'd like to give a try.

Move a UIView based on the content offset for UIScrollView

I am trying to pull down a UIView (Like a Pull Down Menu) when the user scrolls below a Y-offset of 0.0 in the UITable View. If the user pulls down below -80.0 Y-Offset, then the PullDownMenu locks itself , until the User scrolls the other way.
My implementation for just the UITableView's ScrollView is as follows : [lock:false initially]
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(isChangingOffset)
return;
if(resetDrag)
{
[self setScrollViewOffset:scrollView offsetTo:CGPointMake(0, -80.0)];
resetDrag = false;
}
float xx = scrollView.contentOffset.y;
NSLog(#"Offset :%f",xx);
if(xx - begginOffset > 0.0 && lock && !doneDragging)
{
offsetChange = xx - begginOffset;
begginOffset = xx;
lock = false;
[self setScrollViewOffset:scrollView offsetTo:CGPointMake(0, -80.0)];
}
if(lock){
[self setScrollViewOffset:scrollView offsetTo:CGPointMake(0, -80.0)];
}
if(xx <=-80.0)
{
[self setScrollViewOffset:scrollView offsetTo:CGPointMake(0, -80.0)];
lock = true;
}
}
- (void)setScrollViewOffset:(UIScrollView *)scrollView offsetTo:(CGPoint)offset{
- (void)setScrollViewOffset:(UIScrollView *)scrollView offsetTo:(CGPoint)offset{
isChangingOffset = true;
scrollView.contentOffset = CGPointMake(0, -80.0);
isChangingOffset = false;
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
float x = scrollView.contentOffset.y;
begginOffset = x;
doneDragging = false;
if(lock){
resetDrag = true;
}
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
doneDragging = true;
}
Here is a working video of how it looks : Video
The blue color is a UIView that I have added as a subview in the UITableView.
My problem is , I am trying to pull down a UIView,lets call it menuView (that is not a subview of the tableView) based on the UITableView's contentOffset. I could have simply added the menuView in the UITableView like I added the blue color view. But that is only accessible via the table, that is when I scroll to the top and drag it down. But I want the menuView to be 'pull-able' like the notification center at any time.
On using the Y-contentOffset of scroll view, the menuview pull down animation is not smooth. And it stops midway or goes too low. It is jerky and not always the same. How can I implement this ?
Here the sample code for your UIScrollView :
ViewController.h
#interface ViewController: UIViewController {
UIScrollView *scrollView;
}
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
ViewController.m
#implementation ViewController
#synthesize scrollView;
- (void)viewDidLoad
{
[super viewDidLoad];
[self scrollView];
}
- (void)scrollText{
[scrollView setContentSize:CGSizeMake(320, 800)];
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
scrollView.clipsToBounds = YES;
}
And iside you can put wat you want, from code or interface builder.
For a PullDownMenu you can see this GitHub:
MBPullDownController
Hope this help you and simplify your code ;)

Custom Container using UIScrollView with two fingers

I'm creating a container view controller where the child view controllers are shown like a paged scrollView. In this container controller I want to change between pages scrolling horizontally using two fingers. So I used a UIScrollView and I have set it like that:
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.delegate = self;
[self.scrollView setContentOffset:[self rectForPage:1].origin];
for (UIGestureRecognizer *gestureRecognizer in self.scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
I also have implemented the - (void)scrollViewDidScroll:(UIScrollView *)sender method to avoid the vertical scrolling
- (void)scrollViewDidScroll:(UIScrollView *)sender {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x +pageWidth/2) / pageWidth);
self.pageControl.currentPage = page;
[self.scrollView setContentOffset: CGPointMake(self.scrollView.contentOffset.x, 0)];
}
Now the problem is that the childViewControllers subviews can't receive the touches from the user. I want the single touch to be passed throw the view hierarchy. I have read about subclassing UIScrollView to overwrite the method - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view but it's only called if the subviews are UIControl. How I can do it?
The purpose is that this ChildViewControllers have UIButtons inside and ScrollViews that should be used with singleTouches
maybe not exactly, but this might help you. Create a scroll view subclass and implement this;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
for (id subview in self.subviews) {
if ([subview isKindOfClass:[YourViewController class]]) {
YourViewController *VC = (YourViewController*) subview;
if (CGRectContainsPoint(YourViewController.frame, point)) {
return YES;
}
}
}
return NO;
}

Resources