Managing events on a custom UIControl - ios

I am subclassing UIControl to compose a custom control that contains different standard controls.
For this discussion let's assume that my custom UIControl contains a UIButton only.
What I would like to achieve is that clicking anywhere in the custom UIControl generates a click event for that custom UIControl. The standard behavior is that the UIButton will process and consume (i.e. not forward) the click event.
As subclassing UIButton is discouraged, I can't really find a straightforward way of achieving this.
Any suggestions?

I came up with a simple solution that doesn't need subclassing of the UIButton.
In the action method defined for the UIButton's TouchUpInside control event, I have added the following line of code:
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
This results in the TouchUpInside control event being called, when clicking anywhere in the custom UIControl.

UIViews have a method called -hitTest:withEvent: that the event system uses to crawl the view hierarchy and dispatch events to subviews. If you want a parent view to gobble up all events that might otherwise be dispatched to its subviews, just override the parent's -hitTest:withEvent: with something like the following:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if(CGRectContainsPoint([self bounds], point)){
return self;
}
return [super hitTest:point withEvent:event];
}

UIButton is designed to process touch events. You can either set userInteractionEnabled to NO to have the button not accept any touches, or you can use addTarget:action:forControlEvents: on the button to have it call a method on your class when the button is touched.
BTW, where is subclassing UIButton discouraged?

Often I find useful user interaction related tasks in UIResponder class, which is the super class of UIControl - UIButton.
Read about – touchesBegan:withEvent:, – touchesMoved:withEvent:, – touchesEnded:withEvent:, – touchesCancelled:withEvent: in http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIResponder_Class/Reference/Reference.html You may find ways to customize user interaction for your UIButton. By the way, I don't think there will be any problem subclassing UIButton, no matter what you've heard, as long as your implementation is correctly added to the super class implementation, or does responsibly override it altogether.

Related

Exclusive touch on UIControl

I have an UIButton and UISwitch. A user could touch both with one finger each at the same time and release - triggering 2 separate TouchUpInside #IBActions.
How can I tell a control that it's touches must be exclusive, without creating a subclass?
UIControl is subclass of UIView so you can directly set exclusiveTouch = true on any UI control.

Touch events on subclass of UIView as a subview of UIScrollView

I have implemented my own custom subclass of UIView and overridden the drawRect: method.
In my custom view I also want the handle touches, so I also overridden touchesBegan, touchesMoved and touchesEnded.
This works fine but if the number of views on the screen increases then I have to use a UIScrollView as the root view of my UIViewController.
Once my custom UIView becomes the subview of UIScrollView, then it does not receive the touch events. Even though I move my finger within my custom UIView, the scroll view gets scrolled (all my touch events go to the UIScrollView).
How do I solve this problem?
There are several approaches you could try:
Try setting the below properties on the UIScrollView:
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;
See similar SO questions/answers here, here.
Implement hitTest:withEvent:. See here, here.
Use a UIGestureRecognizer. See here, here.
I would personally recommend using a UIGestureRecognizer, but it depends on your specific situation (any of these options may work fine for you).
Have a look at this response from another question: https://stackoverflow.com/a/4629821/193254
You'll have to subclass the scrollview too, and implement that hitTest: method.

Should "clickable" areas in iOS be buttons?

Should "clickable" areas in iOS be buttons or is it ok to just use a generic UIView, UIImage and so on?
Say i have a block of text with an icon, borders, shadows and so on. It looks like a bilboard. What would be the best way to implement that? Using a custom UIButton and just add subviews to it or creating just a generic UIView?
Any thoughts appreciated!
You can simply add UIGestureRecognizers to your UIView and handle them. You can find the documentation here and a tutorial here.
Probably for a view containing multiple subviews, you want to use a UIView subclass. While a UIButton would be OK for adding views, state changes, enabling/disabling may do wonky things to the view as a whole (including the subviews). Using your own UIView subclass will ensure that what gets displayed doesn't get toyed around with by any state changes, giving you complete control. You can override
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
to intercept touches on your custom view. If you're going to do this, remember that the userInteractionEnabled field MUST be set to true.
An additional note: you mentioned shadows as one of the elements in your question. If you're using CALayer to do this, definitely avoid using UIButton, as it's set of layers to handle different states is quite complex.
If the target area is big enough, you could place a transparent UIButton (switch the button type to custom, but don't supply an image) over the top of the clickable view to intercept the taps.

MBProgressHUD disabling interaction with UITableViewController

I'm using an MBProgressHUD inside of a UITableViewController. While I am able to get the HUD to display successfully, the HUD intercepts all of the screen's touch notifications and prevents scrolling in my UITableView.
I know that the intended functionality of MBProgressHUD might be to lock up the interface (say, during a blocking operation), but I want to know how to forward the touch events to the proper places regardless.
Any ideas?
The MBProgressHUD is a subclass of an UIView so you have to disable the user interaction with it. So:
youMBProgressHUDPointer.userInteractionEnabled=NO;
Figured it out. The following code needs to be added to MBProgressHUD's code:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
return (NO);
}
Check my answer here. The MBProgressHUD blocks interactions at application level. You need to devise a way to get around that by putting some custom logic.

Using a UIView as a button

I'm trying to use a UIView I've created in Storyboard as a button. I assumed it would be possible to use a UIButton, setting the type to custom. However I was unable to add subviews to a custom UIButton in Storyboard.
As such I've just spent the last hour reinventing the wheel by making my own custom gesture recoginizers to reimplement button functionality.
Surely this isn't the best way of doing it though, so my question - to more experienced iOS developers than myself - is what is the best way to make a custom button?
To be clear it needs to:
Use the UIView I've created as it's hittable area.
Be able to show a
different state depending on whether is currently highlighted or not
(i.e. touch down).
Perform some action when actually tapped.
Thank you for your help.
You can use a UIButton, set the type to custom, and then programmatically add your subviews...
Change your UIView into a UIControl in the storyboard. Then use the method [controlViewName addTarget:self action:#selector(*click handler method*) forControlEvents:UIControlEventTouchDown];. click handler method is a placeholder for the method name of your handler. Use this method except change out the UIControlEventTouchDown for UIControlEventTouchInside and UIControlEventTouchDragExit to call a method when the user finishes their click and drags their finger out of the view respectively. I used this for something I'm working on now and it works great.
In Touch down you will want to: highlight all subviews
In Touch up inside you will want to: unhighlight all subviews and perform segue or do whatever the button is supposed to do
In Touch Drag Exit you will want to: unhighlight all subviews
See second answer by LiCheng in this similiar SO post.
Subclass UIControl instead. You can add subviews to it and it can respond to actions
Why are you implementing your own GestureRecognizer? I recommend using the UIView so you can add subviews in the interface builder and adding a UITapGestureRecognizer. You can even do this graphically since you don't care about IOS4 support.

Resources