I have been over the iPhone Programming Guide several times regarding the behavior of when the event methods are called in an app.
From what the guide says:
touchesBegan is called when a finger first touches the screen.
touchesMoved is called when a finger on the screen moves.
touchesEnded is called when a finger lifts off the screen.
The issue becomes a little clouded with multiple fingers are involved:
Even with the Multi-touch Interaction flag set to NO, the app continues to call the touchesBegan method of a view that is currently tracking another touch. This seems counter intuitive to me.
Is this the correct behavior? I would think that a new UITouch (even added to the current event being tracked) would not trigger the touchesBegan method.
Of note, I set this flag in IB as well as programatically to make sure I wasn't missing something.
Thanks,
Corey
Yes I believe it is correct behavior. You can track the location of each touch event so I think you just need to structure your logic such that you handle:
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
to get the first touch, and objectAtIndex:1 for the second. I think it goes up to four or five (not sure, please see the docs).
Good luck!
I figured out my problem, but first to clarify proper functionality of multiple touches:
If your view's multipleTouchEnabled flag is set to NO, the touchesBegan method of that view should NOT fire if a second touch is applied to a screen.
With that, the solution to my problem:
My view contains several subviews. The view is responsible for handling touches for itself AND the subviews.
When my code was not functioning properly, I had the subview's userInteractionEnabled = YES. This meant that when a subview was touched, it would forward the touch to the superview, REGARDLESS of whether the superview was tracking another touch.
In other words, although UIApplication respects the multiTouchEnabled flag of a view, other views in the responder chain do not.
Related
I made a slideview using a uiview and detecting touches to move pages. This slideview is almost like this, except that I made it works like a UITableView.
Now I'm using this to uivews with uiscrollviews. The problem is, "how to distribute touch events to scrollview or slideview?". I had the logic to do. Basically, the uiscrollviews are vertical and slideview is horizontal.
I tried hitTest to keep the touchBegan,Moved,Ended in slideview. When I get a touch movement horizontally, I keep to slideview, when vertically, distribute to uiscrollview. But I cannot figure out how to distribute events to uiscrollview.
Calling [scrollView touchesBegan:touches withEvent:event] doesn't work. I supposed uiscrollview has a different way to work.
If you don't find a clue to your answer, probably, you're wrong.
UIScrollView uses a own way to get touchesBegan, Moved and Ended. Way that I don't, but it's mean if you override touchesBegan to make UIScrollView stops to work, you won't get it. Using hitTest in superview of scroll, you can get the touches before UIScrollView but you can't change the touches target while touches is happening.
After all, there is one way to solve this, ashly, three ways.
1- Simulate touches
I didn't test this, you'll know below. Events come from UIWindow and distributed to subview by - (void)sendEvent:(UIEvent *)event. We don't know how touches target is saved, and change this is completely out of question. But we can use the idea of override superview's hitTest to know what the user will do to make a 'WA' to change the target. To do this, simulate a event of touch ended. Supposed target will be reset. Simulate a event of touch begin again, and this time make sure to let hittest get scrollview.
You can find how simulate events here. The problem is, probably your app will be rejected due using private methods.
2- Make your own UIScrollView
This should be the best or the worst, depending what you want to do. I believe it's painful. And isn't what you want to do right now.
3- Surrender to 'Nest UIScrollView'
To make slideshow of pdf, hq, docs and books, it's the best and painless way. Put a UIScrollView inside another and let them reach an agreement of scrolling. http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html
I am launching a simple UIView with a textField - let's call it orderSetNameView - upon a button tap. I wish to make this view modal, but without using a
[UIViewController presentModalViewContoller:animated:].
It seems I could simply set textInputView.exclusiveTouch = YES to achieve that.
Apple documentation says about exclusiveTouch:
A Boolean value indicating whether the receiver handles touch events
exclusively. If YES, the receiver blocks other views in the same
window from receiving touch events; otherwise, it does not. The
default value is NO.
I assume "same window" means same UIWindow, of which I have only one.
The problem is that when I instantiate my orderSetNameView, add it as a subview, and set exclusiveTouch = YES, touch events happen in all other views of my app, i.e., touch events in other views are not blocked as expected.
// ....
[self.view addSubview:self.orderSetNameView];
[self.orderSetNameView openWithAnimationForAnimationStyle:kMK_AnimationStyleScaleFromCenter];
}
// Set as modal
self.orderSetNameView.exclusiveTouch = YES;
Shouldn't orderSetNameView block touch events in all other views? What am I missing?
From Apple developer forums:
exclusiveTouch only prevents touches in other views during the time in which there's an active touch in the exclusive touch view. That is, if you put a finger down in an exclusive touch view touches won't start in other views until you lift the first finger. It does not prevent touches from starting in other views if there are currently no touches in the exclusiveTouch view.
To truly make this view the only thing on screen that can receive touches you'd need to either add another view over top of everything else to catch the rest of the touches, or subclass a view somewhere in your hierarchy (or your UIWindow itself) and override hitTest:withEvent: to always return your text view when it's visible, or to return nil for touches not in your text view.
Put this in AppDelegate or another file. Use this single time.
// Multi Touch Disable
UIView.appearance().isExclusiveTouch = true
UIButton.appearance().isExclusiveTouch = true
I'm trying to make my own custom UIScrollView for some reason. The question is, is it possible to exactly mimic the event handling behavior of UIScrollView including 'delayed content touches' and 'cancellable content touches'? That is, the custom scroll view should delay handling the event until it can determine if scrolling is the intent, and it should also be able to cancel its subviews' touches later. The problem is as follows:
1) hitTest: should return immediately so I can't delay the determination of the touch owner.
2) We can't cancel a touch event later programmatically. There's no such api.
3) I tried to override the sendEvent: method, but it didn't help. Having to call [super sendEvent:] will send events to unintended views. Moreover, hit test has been already done when sendEvent: was called and we can't alter the value of UITouch.view later.
So again the question is, is it possible to make a custom UIScrollView including it's touch handling behavior without using UIScrollView?
Thanks in advance!
One thing to keep in mind is that the gestureRecognizers (Pan and Zoom) that are used for regular UIScrollViews are private properties until iOS 5.0 comes out. I suppose when they are public you could transfer them to your own scroll view.
I have a view, that is able to go back to the previous view.
Let's say this is a questionnaire. So my main view is the questionnaireView(Controller), and it has a subview which shows a question with 2 possible answers. When one answers the question, one presses next, and the questionnaireView(Controller) shows the next question in that particular subview. Simple, right?
Okay, now imagine having to accomodate up to 10 anwers for a particular question. This will require implementing a scrollview in the subview, to accomodate for the question + all answers.
Now my question: I want questionnaireView(Controller) to receive notice of horizontal swipes (next/previous question), but I want all other touches (taps, for the radiobuttons of the answers, and vertical swipes for the scrollview) to go through...
Any idea's how to go about this? I have tried about 5 different approaches and broke my head and motivation on each on of 'em :/
Last one (and most simple one), was simply adding another subview to questionnaireView(Controller), overlaying the question+answer view.
This kindly catches all the touchesBegan/Moved/Ended/Cancelled for me, but even if I just put a forward in -each- of thoses methods ([self nextResponder] ...) the underlying view (with the answers, and the scrollview) won't respond anymore...
I'm kinda lost on this, and am thinking of writing my own Scrollview someday, since UIScrollView is the most terrible monster faced by iPhone devvers :P
Mind you, I am supporting iPhone OS 3.0 and up, so the new gesture APIs are no-go.
I'm not sure what you mean by "a forward in -each- of thoses methods". I'm not sure that nextResponder is the correct thing to forward them to either.
The problem is that touches are supposed to be "owned" by a single view throughout their lifetime. I'm not sure how UIScrollView or gesture recognizers are implemented, but I'm pretty sure they do more than you're supposed to do on your own.
I'd override questionnaireView's hitTest:withEvent: to return self. In your touchesBegan:withEvent:, call [super hitTest:[touch locationInView:self] withEvent:event] and store the subview that "owns" it. In touchesMoved:withEvent:, forward the touch to the relevant subview, but if you detect a gesture, "forget" the subview that owns the touch and instead call touchesCancelled:withEvent:. In touchesEnded/Cancelled:withEvent:, forward it to the subview and then forget the owning subview.
It's icky, but it mostly works. Some things it gets wrong (from the perspective of subviews):
-[UIEvent touchesForView:] will return the QuestionnaireView.
UITouch.view will return the QuestionnaireView
UITouch.phase might not be UITouchPhaseCancelled in touchesCancelled:withEvent: (if you detect the gesture and cancel the touch).
*
I have an application with a view containing several subviews. The subviews did not implement any touchesbegins logic. The Superview implemented all touchesbegins logic and manipulated each subview respectively if it was touched (determined by hit testing).
I have since been converting my subviews to layers. My problem now is that if I touch a layer, the hosting view of the superlayer never gets the touchesbegins method called. However if I touch the background, the touchesbegin method fires.
I understood from documentation that layers cannot handle events, if this is so why would it block events to the hosting view?
Thanks for any help, can't get my head around this.
-Corey
I found the problem... I was releasing the sublayers I was creating using [CALayer layer]. Since I didn't have control of them, I shouldn't have been managing them.
CALayers should not block touch events. Is your userInteractionEnabled flag set in the hosting view (sounds like it is, if you're getting SOME touches)? Is it inside a UIScrollView, which may be doing its own touch-handling.
What class is your touchesBegan method in?
I was having a similiar problem because my touchesBegan method was in a UIView subclass.
After moving the method to a UIViewController subclass, my problem was fixed.
Try doing that.