I am implementing a Google+ type iPhone application. The UI consists of a number of screen-sized panels, and the user can move through these panels horizontally by swiping left or right. I am not using a UIScrollView for this, but am instead implementing it as a series of UIViews on top of a master UIView - I override the touches... events for the UIViews and pass them on to the master UIView to handle repositioning the panels.
My code works perfectly as long as each panel is just an empty UIView (or if it only contains controls that don't pick up touch events, like a UILabel). The problem I'm facing is when I try to put a UITableView on one of the panels. Because the UITableView picks up the touch events, this is interfering with my own code that uses the touch events to slide the panels horizontally.
I have tried two approaches, neither of which is working perfectly:
First approach: I subclass UITableView, overriding the touch... events (touchesBegan, touchesMoved, and touchesEnded). In the overrides, I pass the events on to self.nextResponder and then to super. On my table, I also set canCancelContentTouches = NO;. This works somewhat - if I start swiping with a perfectly horizontal movement, the underlying panel containing the table view moves left and right. However, if I begin the process by swiping vertically, the table view begins scrolling up and down and no horizontal movement happens at all until I lift my finger. Also, if I begin swiping horizontally the left-right movement of the panel happens, but until I life my finger no vertical scrolling of the table view is possible (I'm trying to get a solution where vertical movement scrolls the table view and horizontal movement moves the underlying panel left-right). BTW, without the canCancelContentTouches = NO; call, the touch events stop coming altogether as soon as the table view is scrolled even a little bit.
Second approach: I subclass a UIView, set it's background color to transparent, and then place it over top of the view containing the UITableView. In this overlay subclass, I override the touch events and 1) pass them on to the underlying UITableView and then 2) pass the touch events on to the underlying UIView that handles the left-right swiping. With the approach, the left-right panel movement works perfectly. With the table view, unfortunately, the passed-through touch events allow me to click the table view and change the selected row (so I know the events are at least partially going through), but I can't scroll the table view up or down.
Any suggestions for how to do this correctly are welcome.
Related
I am trying to wrap my head around how this would work. I want to create a horizontal scrollview consisting of "tiles" that are added and that you can scroll through (paging). On each tile will be a main image and other various smaller images that will act as buttons and launch popovers and such. I understand that I can create my "tile" as a uiview subclass programmatically or in a viewcontroller which then can have it's view added to the main uiscrollview. My question is where do I detect touch events of the small button-like elements in each tile? Would the main viewcontroller that manages the scrollview be the one that would handle the touches of the subviews' subviews?
You would detect the touch events for your tiles in whatever view handler you have them in. Basically you would have one view with a scroll view on it, then you would create another view that would have your tiles, within the view that contains your tiles is where you would handle the touch events for them, but you will have to keep track of which "Page" you are on to know what actions should be performed.
This is the best example I have found on using the page controls.
Imagine the situation when you have UITableView placed into UIScrollView.
If you tap on table view and begin vertical moving - UITableView become first responder and will scroll vertically. But if you will move finger horizontally - UIScrollView become first responder and will scroll horizontally.
I have similar situation but instead of UIScrollView I have simple UIView object that intercept - (void)touchesMoved: events and make moving (kinda self made ScrollView).
But how I can do the same trick that UIScrollView do - determine horizontal moving and take away first responder status from UITableView???
Thanks in advance!!!
You could try overriding canBecomeFirstResponder in your view and returning the appropriate value at the appropriate time.
Have you tried throwing a UIPanGestureRecodnizer on the view. You should then be able to set a #selector which will allow you to handle the event in question. Also with pan you can choose to only change the x element and leave the y element alone. Further more with the pan element attached to a view you will not have to worry about having a first responder. Pan elements can a lot of the time be used as a swiping element if you had acceleration and or deceleration. Beyond that I would say you could also add a UISwipeGestureRecognizer element left and right but I do not think you get information such as how far the user swiped meaning, if you want the screen to snap back when the user has not made it far enough to the right or left then you will not have that option. Also, swipe will not give the same effect as the UIScrollView but UIPanGestureRecognizer can, if you do a little extra coding. The extra coding would be observing the position of the x value and then using a little bit of animation for the view to snap to the right or left when the user lets go of the view. Other then that the view should and will follow the finger if you set the x properly.
I have a UIScrollView wich contains 2 views the first at offset 0 and the other at offset 800.
I want the user to be able to scroll down but not always, because in the upper view i have another controls tha receive touch input and dragging and sometimes when you are dragging if your touch is just a little bit out of the control the scrollview scrolls and that's very annoying. So i want that the scrollview only scroll in certain zones.
What you basically need is to subclass the UIView that should intercept touches outside it's frame and override it's hit test method.
I have a view that displays a PDF. It should be zoomable, so I also created a UIScrollView, and in its delegate I implemented viewForZoomingInScrollView to return the PDF view. So far so good.
However, when the user reaches the edge of a zoomed PDF page, I'd like to flip to the next page. Sounds easy, yet I can't seem to figure out how to do it.
I've tried some different approaches:
Using scrollViewDidScroll to detect if scrolling has reached the edge. The problem here is that if zoomScale is 1, and therefore scrolling is not possible, then this function is never called. But the UIScrollView still swallows all touch events, so I also can't detect reaching the edge in touchesMoved. Setting canCancelContentTouches to NO when not zoomed is not an option, as that would also prevent zooming in.
Subclassing UIScrollView, and forwarding some of the touch events to the next responder. Unfortunately when UIScrollView detects a drag operation and cancels the touch, touchesMoved and touchesEnded are not called even for the UIScrollView subclass anymore. Again, setting canCancelContentTouches to NO is not good, as that would also prevent some desired UIScrollView functionality.
Creating a transparent view on top of the scroll view (as a sibling of it), so that this view gets all touch events first, and then forwarding some of the touches to the scroll view. Unfortunately the scroll view doesn't respond to these calls.
I can't use touchesShouldCancelInContentView, becasue that doesn't get the actual touches as an argument, and whether or not I want the scroll view to handle the touch event also depends on the properties of the touch event itself (eg. a touch movement in a direction in which we're already at the edge should not be cancelled by the scroll view, but a movement in the other direction could be).
Looks like whatever UIScrollView is doing is not initiated from touchesBegan / touchesMoved, but instead it gets some notifications about the touches way before that. Possibly in some undocumented way that I can't intercept, nor reproduce.
So is there any way to get notified about all touch movements done over a UIScrollView, while still being able to use (when certain conditions apply) the UIScrollView for zooming and scrolling?
Ok, so here's what I did in the end:
Leaving all scrolling and zooming up to UIScrollView, and handling page turning in the UIScrollViewDelegate's scrollViewDidEndDragging:willDecelerate: is almost a solution, except that this function is never called if the whole content is on-screen, so dragging / scrolling is not possible.
Swipes in this case are handled in a ViewController's touchesBegan / touchesEnded functions, but for this to work, we need to make sure that the UIScrollView does not cancel these events. However, in other cases the UIScrollView should be able to cancel touches so that it can do zooming and scrolling.
The UIScrollView should be able to cancel touches if:
Scrolling is possible (and needed) because the whole content doesn't fit on screen (zoomScale > 1 in my case),
OR
The user touched the screen with two fingers, so that zooming in and out works.
When scrolling is not possible, and the user single-touched the screen, then touches should not be cancelled, and touch events should be forwarded to the view controller.
So I created a UIScrollView subclass.
This subclass has a property pointing to the ViewController.
Using the touchesXXX methods I keep track of the current touch count.
I forward all touch events to the ViewController.
And finally, I've overridden touchesShouldCancelInContentView:, and return NO when zoomScale <= 1 and touchCount == 1.
I have a problem in the touchesMoved handler with a view that is added onto a UIScrollView. I add a number of labels to the scroll view. Each of these labels contain some text and, on swiping my finger on the labels, I have to play a specific file for that text.
If I just add the view onto the window directly, I get all of the touch events in touchesMoved without any problem. When I add my view onto the UIScrollView and then add this to the window, there is some lag in the touchesMoved handler. I am not getting continuous touch points in touchesMoved as with the normal view. As a result, while swiping the finger from the view, it happens that some labels are missed.
Is the problem due to scroll view? The same code runs perfectly in normal conditions (without a scroll view).
Does anyone have any solution to this?
UIScrollView sets a timer on touchDown to be able to know if it should handle scrolling or if it should pass the events on to subviews.
There is a property on UIScrollView for controlling this behaviour:
#property(nonatomic) BOOL delaysContentTouches