touchesBegan in UIView not being called - cocoa-touch

I know that this question must have been answered plenty of times already, but I have no idea why the touchesBegan: is not called in my UIView.
Before I added it in my app, I made a trial project to do some tests. What I did was subclass a UIView where the touchesBegan: is implemented and add this as a subview of another view. The superview has a button which shows the subclassed UIView once clicked. Everything works as expected.
However, when I did the same thing in my existing app, the touchesBegan: method, as well as the touchesMoved:, were never called. Can anyone explain what could be preventing the methods from being called?

I just found the answer to this. I realised that the superview of the UIView has the userInteraction set to disabled (which is the default value). So when I enabled the userInteraction of the superview, the touches are now recognised.

In my case background = UIColor.clearColor() was the reason why touchesBegan was not called. Its obviously not called on transparent elements.

I was struggling on this problem for a while... and i figure it out. I think that the touchesBegan override function listen on the main view: the first one on the storyboard tree of the ViewController! I would love to post an image of my storyboard to be more clear, but i can't! ;)
Anyway, IF you have subviews, they may covers the main view... in this manner the touch wont "reach" the main view.
To avoid this you'll have to set the Background property in the Attribute Inspector to Default for ALL THE SUBVIEWS. In this way the background will be transparent and the touch will be able to reach the main view.
If you don't want to set the background to transparent there is something you can do:
-Add an outlet of your last view (with background set)
-Add a tap gesture recognizer to it
-Handle the tap disposing the keyboard
ES.
In properties:
//The last view's outlet
#IBOutlet weak var ContainerView: UIView!
In viewDidLoad():
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
self.ContainerView.addGestureRecognizer(tap)
Outside:
func handleTap(recognizer: UITapGestureRecognizer){
self.view.endEditing(true)
}

Related

scrolling a scrollView inside of a textView delegate

I have a UIScrollView and contextView. I add UITextViews to it and set the delegate to self so that I can use the textViewShouldBeginEditing method. The textViewShouldBeginEditing is inside the same ViewController.m file, so the VC should be the delegate.
The textViewShouldBeginEditing method gets called and I want to check if the keyboard is blocking the textView and scroll it up if it's being blocked.
I want to call:
[scrollView setContentOffset:CGPointMake(x, y) animated:YES];
However scrollView is not seen in textViewShouldBeginEditing.
I've seen where KVO can be used, but I want to use the delegate methods.
How can I gain access to the scrollView that the textView is on?
You can use IQKeyboardManager. Just drag and drop library to your project and eveerything will managage itself. here is the github link for that. IQKeyboardManager. Hope this will help you.

ViewController calling touchesMoved only once

I have a question that I've searched but can't find a definative answer to. Here is my layout:
UIView - ViewController
|_UIScrollView - added programatically
| |_UIView to hold a backgound/perimeter - added programmatically
|_UIView 1 - added programmatically
|_UIView 2 - added programmatically
and so on
My question is how come the ViewController calls "touchesMoved" only once when I move say UIView 2 on touch?
Now UIView has it's own touchesMoved method, but I need the controller's touchesMoved to get called as I need it to talk to the ScrollView to update its position. Such as when that UIView 2 is near the corner, so that the ScrollView moves a little to fully show UIView 2.
If there is no way around this is there a way to update ScrollView from UIView 2 to scroll when its near a corner?
Edit:
I think I may have found a work around. Not sure if this will be accepted by Apple but:
I just made a call to a instance variable that is = to self.superview which then allows me to talk back to my ScrollView within UIView's touchesMoved
in that i can call the method [ScrollView setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated] so my ScrollView gets updated as the subview(UIView2) moves close to the edge of the UIWindow.
Thank you for the suggestions.
The behavior you describe is the result of the UIScrollView hijacking the touch moved event. In other words, as soon as the UIScrollView detect that a touch moved event falls within its frame, it takes control of it. I experienced the same behavior while trying so create a special swipe handler, and it failed each time a UIScrollView was also interested by the swipe.
In my case, I solved the issue by intercepting the event in sendEvent: overridden in my custom UIWindow, but I don't know if you want to do the same. In any case, this is what worked for me:
- (void)sendEvent:(UIEvent*)event {
NSSet* allTouches = [event allTouches];
UITouch* touch = [allTouches anyObject];
UIView* touchView = [touch view];
//-- UIScrollViews will make touchView be nil after a few UITouchPhaseMoved events;
//-- by storing the initialView getting the touch, we can overcome this problem
if (!touchView && _initialView && touch.phase != UITouchPhaseBegan)
touchView = _initialView;
//-- do your own management of the event
//-- let the event propagate if you want also the default event management
[super sendEvent:event];
}
An alternative approach that you might investigate is attaching a gesture recognizer to your views -- they have a pretty high priority, so maybe the UIScrollView will not mess with them and it might work better for you.
If there is no way around this is there a way to update ScrollView from UIView 2 to scroll when its near a corner?
Have you tried to make the UIScrollView scroll by calling:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated

Change self.view's class type

I have a app out for testing right now that's almost completely done - just a few bug fixes left. Unfortunately, the customer decided that they'd like the entire main page of the app to be inside of a scroll view (so that there's more room for the table at the bottom). I already have everything set up and I don't really want to move everything and change references. Is there an easy way to change the class of the main view to a scroll view? I've already tried changing the class in IB and setting the class type in the init method. If there isn't I'll probably just throw the top section of the view into a nib file and load it as a custom cell.
-EDIT- I ended up changing the class type in IB and then doing
[(UIScrollView *) self.view setScrollEnabled:YES];
[(UIScrollView *) self.view setContentSize:CGSizeMake(0,2000)];
in viewDidLoad. Thanks for the help, wish I could accept all your answers.
When you are referring to [self view], I am going to assume you mean in a view controller. The view of a view controller can be any view that derives from UIView. Thus a scrollview is completely acceptable.
I don't really want to move everything and change references.
what would you have to move? why would you have to change references? Only thing you should need to do is add a scroll view to your view controller, set the view controllers view to it, and add the current view as a subview to the new scroll view. No references need to be changed, nothing has to be moved.
Refer to loadView method in documentation of view controller.
Here is a simple (untested!) example
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init] autorelease];
//Set the properties of scrollview appropriately....
self.view = scrollView;
}
Now the root view of your view controller will be a scroll view.
Note
- As the documentation states, do not do this if you are using interface builder to initialize your views/view controller. I could not tell from your description if this was the case or not. If it is, you should be able to change the view type in interface builder.
You need to set the contentSize property of your scrollview.
Since you are using IB, the easiest way to do this is to put all your UI elements into a view and add this single view to your scroll view. In the viewDidLoad method, set the content size of the scrollview to be the same size as the view that contains all your UI.
As an aside, there are much easier ways to reference views than walking down the view hierarchy, as you seem to be doing. viewcontroller.view.something.tableview. Add a connection to the tableview from your view controller in IB and it doesn't matter where that tableview is in the view hierarchy. You'll always be able to reach it from viewcontroller.tableview, no matter how you rearrange your nibs.
I think you have to use a pointer with proper type. Example for Google Maps: let's say you changed you base view's class to GMSMapView.
MapViewController.h
#property GMSMapView *mapView;
MapViewController.m
-(void)awakeFromNib{
[super awakeFromNib];
self.mapView = (GMSMapView*)self.view;
// ... etc.
}

Control+Click not triggering menuForEvent

All,
So I have a subclass of NSBox and have subviews in it like a label and two imageViews. I have overridden menuForEvent: in it. However, when I click on the NSBox to select it and then later Control+Click on any of its subviews then menuForEvent: is never called.
I don't understand why that is the case.
There is a difference in how control-clicks and right-clicks are handled by NSView (as jfewtr pointed out). Contextual menus will appear for a right-click if the click falls within a subview, but not for a control-click.
I was surprised by this and actually wrote a post about it with more details here: NSView control-click quirks
There are a couple potential solutions, but overriding/customizing your entire subview tree is probably not the best choice. I've found the best fix for this is to display your contextual menu explicitly in your top-level view (your NSBox subclass) for a control-click:
- (void)mouseDown:(NSEvent *)theEvent
{
if (theEvent.modifierFlags & NSControlKeyMask)
{
[NSMenu popUpContextMenu:[self menuForEvent:theEvent] withEvent:theEvent forView:self];
}
}
While it's not great to hardcode this behavior, it avoids manipulating or traversing your entire subview tree, which can incur more problematic side effects/bugs.
You need to implement menuForEvent: in the subviews too, and forward the event to your superview's (NSBox subclass) implementation of menuForEvent:
- (NSMenu *)menuForEvent:(NSEvent *)event
{
return [[self superview] menuForEvent:event];
}
I assumed that it would automatically fall through to the superview without the need for subclassing the subviews. I found that a right-click does, but, for some reason, a control-click does not.

NSView Subviews interrupting drag operation

I have an NSView which is registered for a drag operation.
In that view I have a subclassed NSScrollView, which in itself has an NSImageView in it.
When dragging onto the original NSView, everything is fine, other than when I drag over the aforementioned NSImageView, which seems to interupt the drag and I cannot drop onto it (or in fact, the view underneath it.
The NSScrollView appears to ignore the drag and allows that to go through to the underlying NSView, but how can I do that for the NSImageView so that the Drag/Drop registers through itself, it's superview (the NSScrollView) and onto the underlying NSView.
You can unregister the NSImageView as a dragging destination. Its superview will then handle the dragging session, if it's set up to do that.
[imageView unregisterDraggedTypes];
Oh, thanks so much #Steven!
Swift 4.2 and #IBOutlets:
#IBOutlet private weak var imageView: NSImageView! {
didSet {
imageView.unregisterDraggedTypes()
}
}
Long story:
I had this NSCollectionView, which loaded a custom NSCollectionViewItem from a XIB file.
The collection accepts drop operations (collectionView.registerForDraggedTypes([.fileURL])).
But as soon as something was dropped into it and the custom cell was "dequeued" (makeItem(withIdentifier:for:)) -- drop operations stopped working above that specific cell.
unregisterDraggedTypes() fixes the issue indeed. 🎉