UIView, how to determine when touches entered the view - iphone

It appears that all the touch methods of a UIView are only called if the touches began within the bounds of that view. Is there a way to have a view respond to a user who has touched outside the view, but then dragged his fingers into the view?
In case it matters, my specific application is for dragging a MKPinAnnotationView (using built-in 4.0 dragging). I want something to happen if the user drags a pin onto another view (which happens to be an AnnotationView as well, but it could be anything). No method for dragging is called until I let go of the pin; and no method no the UIView that's being dragged to seems to be called unless I started by touching from within the view.
Because the superview is a MKMapView, it is difficult to just use the touchesMoved event of that and check if the user is in the right location or not. Thanks!

So after playing around with it for a while, I found that the answer given here actually gave me what I needed, even though the question being asked was different.
It turns out you can subclass UIGestureRecognizer; and have it handle all the touches for the view that it has been added to (including an MKMapView). This allows all the normal MKMapView interactions to still behave without any problem; but also alerts me of the touches. In touchesMoved, I just check the location of the touch; and see if it is within the bounds of my other view.
From everything I tried; this seems to be the only way to intercept touchesMoved while the user is dragging an MKAnnotation.

You sure can:
(HitstateView.h)
#import <UIKit/UIKit.h>
#interface HitstateView : UIView {
id overrideObject;
}
#property (nonatomic, retain) id overrideObject;
#end
(HitstateView.m)
#import "HitstateView.h"
#implementation HitstateView
#synthesize overrideObject;
- (void)dealloc {
self.overrideObject = nil;
[super dealloc];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
return overrideObject;
}
return hitView;
}
#end
Make this view the size of your touch area. Set the overideObject to the view you want the touches to go. IIRC it ought to be a subview of the HitstateView.

Every view inherits UIResponder so every view gets touchesBegan/Moved/Ended - I do not think starting the touch outside the view means the view gets no event when the touch moves over the view. If you want to get a notification that something has been dragged onto your MKMapView you should make a subclass that handles the touch but then passes the event to super, allowing the hierarchy to do whatever it needs to do with the touch. You don't need to capture or modify the event just observe it.

It depends on how your views are set up. Generally leveraging the responder chain is the best way to go. It allows you to play tricks, though it may be too specific to address your particular needs.
You can also play tricks with forward events by override hit testing:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/MultitouchEvents/MultitouchEvents.html%23//apple_ref/doc/uid/TP40009541-CH3-SW3
Your particular case sounds pretty exotic, so you may have to play tricks like having a parent view whose frame is large enough to contain both views in question.

Related

MKMapview with UIGestureRecognizers

So I have a UIView as my Root view on my iPad. I add some subviews to it, amongst which there's also an MKMapView.
The thing I am trying to achieve ist to detect a 3-finger swipe across the screen so I can react to it. Furthermore I need to distinguish between 3-finger swipe to the left or to the right.
Before I added the mapview, I was experimenting with touchesMoved etc. Since I found out this to be inaccurate, I moved to using UISwipegestureRecognizer which worked well.
Anyway, once I added the Map, it ate all my touches. So I kept looking for answers.
This one seemed promising:
Intercepting/Hijacking iPhone Touch Events for MKMapView
as well as subclassing UIWindow and intercepting the touches.
Well, it turns out, none of them work well for me, since in both cases I end up either in
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
or in the situation to recognize the touch type etc.
This is, what I was trying to do in first place!
So my question is: Is there a way to use UIGestureRecognizers the way I described above to maintain my controls while keeping the functionality of the Mapview?
Have I decribed my problem accurately enough?
Greetz.
This answer describes subclassing a gesture recogniser to ensure that it does not interfere with other recognisers.
You could add a custom recogniser to MKMapView that intercepts three finger swipe (and reacts appropriately) but then allows all other gestures to be processed as normal by the MKMapView.
The link above give the example adding simple finger press by subclassing UIGestureRecognizer. For a three finger swipe I would subclass UISwipeGestureRecognizer.
I have had success using this method to add two finger rotate to a MKMapView without messing up pan and zoom etc.
The map should not "eat" all your touches. There's something wrong with that - I have successfully added long press and tap recognizers to the MKMapView, and they worked as expected.
Make sure to assign your controller the delegate of all the gesture recognizers you create, and be sure to implement the method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer :(UIGestureRecognizer *)otherGestureRecognizer;
You can gain further control by implementing
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
After that, it's very much up to the details of your implementation to make the gesture recognizer work with the map view. In my experience you can add your own, and it should not be interfering with your recognizers.
Some other thoughts:
- Overlay an invisible user interaction enabled view over the MKMapView to take events from it. Depending on how much interaction you need on the map view you could turn this on and off, etc.
- Add another swipe recognizer to the map view itself (in addition to the one on the parent view).
You could also play around with UIView hitTest method but for me that was always too low level.
question is old, but actual. my two cents, especially for OSX
If You need to detect pinch from region changes by code:
(I set an ivar to detect in in orther places of controller code)
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
guard let view = mapView.subviews.first else{ return }
self.regionChangeIsFromUserInteraction = false
#if os(iOS)
guard let recognizers = view.gestureRecognizers else{ return }
#elseif os(OSX)
// on OSX they are in maps..
let recognizers = mapView.gestureRecognizers
#endif
for recognizer : AimGestureRecognizer in recognizers{
let state = recognizer.state
if state == .began || state == .ended {
self.regionChangeIsFromUserInteraction = true
break
}
}
#if DEBUG
print("regionChangeIsFromUserInteraction ", self.regionChangeIsFromUserInteraction)
#endif
}
Would removing the swipe gesture recognizer from your MkMapView resolve the issue?
UISwipeGestureRecognizer *leftSwipeGesture = [[UISwipeGestureRecognizer alloc] init];
UISwipeGestureRecognizer *rightSwipeGesture = [[UISwipeGestureRecognizer alloc] init];
leftSwipeGesture.direction = UISwipeGestureRecognizerDirectionLeft;
rightSwipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[YourMKMapView removeGestureRecognizer:leftSwipeGesture];
[YourMKMapView removeGestureRecognizer:rightSwipeGesture];
That way, MKMapView will not respond to swipe gestures and resigns responder to the container view controller of the MKMapView.

Is there hitTest:(CGPoint) in CAShapeLayer?

I have a multiple CAShapeLayers in a view and I'd like to drag it.(shape are irregular) Please give some suggestions for doing it.
CALayer and its subclasses are not part of the responder chain, and they do not descend from UIResponder. Therefore, touchesBegan:withEvent:, etc. will never be called on any CALayer subclass. You need to detect the touch on one of the hosting UIViews in the layer hierarchy. Then, you use hitTest: in the touch handlers to detect which layer was touched.
It will be easier on you if you create a UIView subclass with a CAShapeLayer as its backing layer like this:
#implementation MyShapeView
- (CALayer *)layerClass {
return [CAShapeLayer class];
}
#end
Then, add instances of your custom view as subviews to your main view. After that, you can use the UIResponder methods or, even better, a UIGestureRecognizer to handle the dragging. I highly recommend using gesture recognizers if you can target iOS 3.2 or higher. They make event handling much simpler.

uiwebview not responding / pass touches to super

So I wanted to make my UIWebview respon to touch events. I have read several topics about this. I created a Subclass of UIWebView, added it so my UIViewController and linked it via IB.
In my Subclass, i overrode touches ended and called [super touchedEnded]. didnt help.
I overrode hittest, but it doesnt call super either!
If I add this code to my subclassed UIWebView:
#implementation UIWebView (CustomView)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"hit");
return self;
}
#end
then the touches ar forwarded to the superclass, but hittest is not(!)and additionally the webview stops scrolling.
Well, I want so recognize a touch in my webview and pass it to super - what am I doing wrong?
EDIT:
I'm wondering, why so many suggestions include using 'hittest' - this results in being unable to scroll your webview.....any ideas?
Answering my own question here - this is the version that did what I wanted: The right way. This is about subclassing UIWindow and passing on the events caught.
I would still love some explanations to my previous questions tho :P

Is there an easy way to drag and drop a UITableViewCell between two UITableViews?

As the title says. Reordering within a single UITableView is trivial, but the screen of the iPad is large enough to display multiple UITableViews at the same time. So it seems like there should be a way to drag and drop a UITableViewCell between two UITableViews. Any thoughts on the best approach?
My solution was to use a custom UIGestureRecognizer for tracking the touch events, and a separate view for drawing the dragging operation.
This works because UIGestureRecognizer doesn't block the responder chain.
From the UIGestureRecognizer documentation:
UIGestureRecognizer objects are not in the responder chain, yet observe touches hit-tested to their view and their view's subviews.
Create a custom UIViewController (DragAndDropViewController) and add its view to the parent view of the views you want the drag & drop operation to occur in. Use your custom gesture recognizer class to forward the touch information to your DragAndDropViewController.
The source tells your DragAndDropViewController where the the drag originates from (and any custom info). The controller should also have a delegate reference to the drop destination. When the drop occurs, send the delegate the UITouch event (not recommended according to Apple, but the UITouch object is needed to get the correct location in the destination view).
This is what my DragAndDropViewController looks like:
#protocol DragAndDropDestination
(void)droppedItem:(NSDictionary *)item withTouch:(UITouch *)touch;
#end
#interface DragAndDropViewController : UIViewController
{
id _dropDestination;
NSDictionary *_draggedItem;
UIImageView *_icon;
}
#property (nonatomic, assign) id dropDestination;
#property (nonatomic, retain) NSDictionary *draggedItem;
// Source sends this message
- (void)startDraggingWithItem:(NSDictionary *)item;
#end
Once the destination gets the message, you can use - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event to get the exact view at the drop destination.
Also make sure you disable cancelsTouchesInView in the gesture recognizer if you want other UI operations to happen normally in your table views.

Hide keyboard when losing focus off UISearchBar

I'm not sure why this is so hard. I have a UISearchBar at the top of my page. When the user types into that UISearchBar, it will automatically filter the results in the UITableView that is below the search bar. There is no need for a Search button since I search when keys are pressed.
The flow of the application is that the user types a search in the search bar (with the keyboard being displayed) and then will scroll the results in the table view - at which point the keyboard needs to disappear.
I'm having a hard time getting the keyboard to disappear.
I know I need to do:
[searchBar resignFirstResponder];
to get the keyboard to disappear but I can't find what delegate I need to perform this on. I want to do this as soon as the user touches the table view.
Any ideas?
Try this:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[searchBar resignFirstResponder];
}
It worked for me
I think the easiest (and best) way to do this is to subclass your global view and use hitTest:withEvent method to listen to any touch. Touches on keyboard aren't registered, so hitTest:withEvent is only called when you touch/scroll/swipe/pinch... somewhere else, then call [self endEditing:YES].
This is better than using touchesBegan because touchesBegan are not called if you click on a button on top of the view. It is also better than using a dim screen because in a complexe and dynamic user interface, you can't put dim screen every where. Also, it's better than calling [searchBar resignFirstResponder], because you may have many text fields on screen, so this works for all of them.
You might want to do it as Cydia (jailbroken packaging UI) does it - there is a search button, and when you press the search button, it closes the keyboard. The results are still filtered as you type for a preview.
Since the results are going to be in a table view, use UIScrollView's delegate (to which UITableView responds) method - (void)scrollViewDidScroll:(UIScrollView *)scrollView.
You'll need to have your view controller respond to the UITableView's data source and delegate (in the .h file):
<UITableViewDataSource, UITableViewDelegate>
Than in your initialization method in the .m file add:
self.tableView.dataSource = self;
self.tableView.delegate = self;
Finally, add this method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self.view endEditing:YES];
}
This will make the UISearchBar lose focus, and the keyboard will hide.
One possibility is that the UISearchBar belongs to a controller, that is displayed in UIModalPresentationFormSheet.
I had the same problem and found out that this is related to the method "disablesAutomaticKeyboardDismissal" of UIViewController.
The Apple documentation states the following:
Override this method in a subclass to allow or disallow the dismissal
of the current input view (usually the system keyboard) when changing
from a control that wants the input view to one that does not. Under
normal circumstances, when the user taps a control that requires an
input view, the system automatically displays that view. Tapping in a
control that does not want an input view subsequently causes the
current input view to be dismissed but may not in all cases. You can
override this method in those outstanding cases to allow the input
view to be dismissed or use this method to prevent the view from being
dismissed in other cases.
The default implementation of this method returns YES when the modal
presentation style of the view controller is set to
UIModalPresentationFormSheet and returns NO for other presentation
styles. Thus, the system normally does not allow the keyboard to be
dismissed for modal forms.
In my case, I displayed the search bar within the navigation bar, so I had to create a subclass of UINavigationViewController that overrides the method as follows:
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}
After that resignFirstResponder would finally make the keyboard disappear.
Might be super late to answer but this might help anyone in future. This particular piece of code worked for me. P.S. I don't own this solution, found some time ago on the net itself.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if ([self.srchBar isFirstResponder] && [touch view] != self.srchBar)
{
[self.srchBar resignFirstResponder];
}
[super touchesBegan:touches withEvent:event];
}
Given a view with a searchBar variable, add this method to the view:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
if searchBar.isFirstResponder && !searchBar.point(inside: point, with: event){
searchBar.resignFirstResponder()
}
return super.hitTest(point, with: event)
}