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;
}
Related
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;
}
}
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!
I have added 20 subviews to scrollview line by line as rows
yPos=0;
for (int i=0; i<24; i++) {
UIView *timeView=[[UIView alloc]initWithFrame:CGRectMake(71, yPos, 909, 60)];
timeView.userInteractionEnabled=TRUE;
timeView.exclusiveTouch=YES;
timeView.tag=i;
NSLog(#"sub vieww tag=:%d",timeView.tag);
timeView.backgroundColor=[UIColor whiteColor];
UILabel *lbltime=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 70, 60)];
lbltime.text=#"VIEW HERE";
lbltime.textColor=[UIColor grayColor];
// [timeView addSubview:lbltime];
[scrlView addSubview:timeView];
yPos=yPos+61;
}
Now when ever I tap on a subview I am not getting the tapped subview properties.
like coordinates. It is giving parent view coordinates
I enabled subview UserInteractionEnabled to Yes.
Can any one tell me how to get tapped subview coordinate and tag value?
DO NOT subclass from UIScrollView, that's exactly why there are gesture recognizers. Also, DO NOT add a separate gesture recognizer to each view.
Add one gesture recognizer to your scroll view, and when it's clicked use the x,y values of the touch to calculate which view was clicked.
You'll need to do a small calculation: (y value of the click + UIScrollView y offset) / 60.
60 is the height of each view. This should return the index of the clicked view.
EDIT:
Code example:
- (void)viewTapped:(UIGestureRecognizer*)recognizer
{
CGPoint coords = [recognizer locationInView:recognizer.view];
int clickedViewIndex = (self.offset.y + coords.y) / 60;
// now clickedViewIndex contains the index of the clicked view
}
UIView *v = recognizer.view;
int tagNum = [v tag];
Using the tagNum you can do your further operatins.
Or v is your object of tapped view.
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
tap.numberOfTapsRequired = 1;
[timeview addGestureRecognizer:tap];
Add this in for loop only.
Make a class extending UIScrollView :
For example :
.h file :
#protocol CustomScrollViewDelegate <NSObject>
#optional
// optional becuase we always don't want to interact with ScrollView
- (void) customScrollViewTouchesEnded :(NSSet *)touches withEvent:(UIEvent *)event;
- (void) customScrollViewDidScroll;
#end
#interface CustomScrollView : UIScrollView
#property (weak, nonatomic) id<CustomScrollViewDelegate> touchDelegate;
// delegate was giving error becuase name is already there in UIScrollView
#end
Code for .m file :
#import "CustomScrollView.h"
#implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesEnded");
if (!self.dragging) {
//NSLog(#"touchesEnded in custom scroll view");
if (_touchDelegate != nil) {
if ([_touchDelegate respondsToSelector:#selector(customScrollViewTouchesEnded:withEvent:)]) {
[_touchDelegate customScrollViewTouchesEnded:touches withEvent:event];
}
}
}
else {
// it wouldn't be called becuase at the time of dragging touches responding stops.
[super touchesEnded:touches withEvent:event];
}
}
#end
Implementing this use subview of scroll view, it will work
for (UILabel *label in [customScrollView subviews]) { // change name of table here
if ([label isKindOfClass:[UILabel class]]) {
if (label.tag == savedtag) { // use your tag
// write the code as desired
}
}
}
I have a viewController which contains a UITableView. This viewController implements UITableViewDelegate, and UITableViewDataSource, and I'm also trying to implement the following methods:
touchesBegan
touchesMoved
touchesEnded
However, these methods are not being called. I am trying to call these methods in response to the user touching a UIImageView that is inside a UITableView, but doing so only calls this method:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.view bringSubviewToFront:_imageView];
[UIView animateWithDuration:.3 animations:^{
CGRect rect = [self.view convertRect:[tableView rectForRowAtIndexPath:indexPath] fromView:tableView];
CGFloat floatx = _imageView.frame.origin.x - rect.origin.x;
_imageView.frame = CGRectMake(rect.origin.x + floatx, rect.origin.y, _imageView.frame.size.width, _imageView.frame.size.height);
}];
NSLog(#"Table row %d has been tapped", indexPath.row);
}
What I am hoping is to continue to call this method, but also call:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == _imageView) {
//need to make sure user can only move the UIImageView up or down
CGPoint location = [touch locationInView:_imageView];
//ensure UIImageView does not move outside UIITableView
CGPoint pt = [[touches anyObject] locationInView:_imageView];
CGRect frame = [_imageView frame];
//Only want to move the UIImageView up or down, not sideways at all
frame.origin.y += pt.y - _startLocation.y;
[_imageView setFrame: frame];
_imageView.center = location;
return;
}
}
when the user is dragging the UIImageView inside the UITableView.
Can anyone see what I am doing wrong?
Thanks in advance to all who reply
I had a similar problem to this except I was trying to implement touch detection to an UIWebView. I eventually got over it by adding a UIPanGestureRecognizer to the UIWebView's .view property.
Implement this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
and then add the UIPanGestureRecognizer to the UITableView like so:
UIPanGestureRecognizer *panGesture;
panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureDetected:)];
panGesture.maximumNumberOfTouches = 1;
panGesture.minimumNumberOfTouches = 1;
panGesture.delegate = self;
[self.tableView addGestureRecognizer:panGesture];
and then implement the method
- (void)panGestureDetected:(UIPanGestureRecognizer *)recognizer {
}
Now, every time the UIPanGestureRecognizer detects a pan gesture, it will call that method and you can get the location of the UIPanGestureRecognizer by calling the method [recognizer locationInView:self.tableView.view]; which will return a CGPoint. You can also get the state of the UIPanGestureRecognizer by calling [recognizer state] which returns a UIGestureRecognizerState.
Have you set userInteractionEnabled on your view that the touches will be inside?
I have the following structure of the views
UIView(1)
|--> UIScrollView
|-----------> UIView(2)
|------> UIButton
hitTest in UIView(1) has been overridden with the following:
- (UIView *)hitTest:(CGPoint) point withEvent:(UIEvent *)event
{
/* this view should have only view only: the scroll view */
UIView * vv = [[self subviews] objectAtIndex:0];
if ([vv pointInside:point withEvent:event])
{
UIView *rv = [vv hitTest:point withEvent:event];
return rv;
}
if ([self pointInside:point withEvent:event])
{
return vv;
}
return nil;
}
This is required in order to catch UIScrollView dragging outside of the scroll view itself.
The issue is that when the UIButton is clicked, the event attached to it does not fire. The view returned from the hitTest is UIView(2).
How can I make the UIButton to respond to clicking event?
Edit
After suggestion of Mundi, I exchanged hitTest with the following and it works. Of course, I forgot to call [super hitTest:].
- (UIView *)hitTest:(CGPoint) point withEvent:(UIEvent *)event
{
/* this view should have only view only: the scroll view */
UIView * scrv = [self findScrollView];
UIView *superView = [super hitTest:point withEvent:event];
if (superView == self)
{
return scrv;
}
return superView;
}
How about calling [super hitTest:point withEvent:event] at the appropriate point?