I was trying to implement a container view controller design into my app. However I was told that I need to support iOS 4.3 devices, so the official view controller API introduced in iOS 5 is not an option at the moment.
In order to achieve a similar behaviour I used a hack. Resized the view for my RootViewController and the added a subview to it that it's outside the view's bounds. For example: RootView has bounds 0,0,320,480. Now I resized it to 0,0,320,430 and included a subview at 0,430,320,60. This works since I do all calculations using the ApplicationFrame giving me stable frames on which to work. But the problem I'm facing right now is that the subview which is out of the bounds of the view is not receiving touch events. The maskToBounds = NO property helps me with the display. But touches? Anybody know how to do that?
Whenever you want the subview to receive touch events in such cases, you can do the following:
1- Create a new class that inherits from UIView and override hitTest:withEvent: to allow subviews to intercept touches:
#interface CustomView : UIView
#end
#implementation CustomView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
/// Check if the point is inside the subview
CGPoint newPoint = [subview convertPoint:point fromView:self];
if ([subview pointInside:newPoint withEvent:event]) {
/// Let the subview decide the return value
return [subview hitTest:newPoint withEvent:event];
}
/// Default route
return [super hitTest:point withEvent:event];
}
#end
2- Change the class of root view to our CustomView (from the right panel in Xcode > Identity Inspector > Custom Class).
And we're done!
Related
I've got a rootView. Now I add a subview to my rootView by using the method addSubView:. After that the subview is added consisting of a view containing several buttons. Now what I want is, to be able to press both, the buttons on my rootView and the buttons on my subView. However when I turn off user interaction of my subView, I can't press its buttons any more. However if I let it on, I cant press the buttons of my rootView.
Can anyone help me?
For your "subview," subclass UIView and override the the hitTest method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *subview = [super hitTest:point withEvent:event];
if ( subview != self )
return subview;
else
return nil;
}
This will cause the buttons and other views within your "subview" to respond to events, but the view itself will act as if it's not there.
I have UILabels animating on my mainView and I want to have a custom UIView pop onto the main view when a button is pressed. The labels animating in the background continue to animate and the problem that I'm having is that the labels animate on top of my custom UIView.
Does anyone know how I can ensure that my custom UIView is the front-most view so that the UILabels animate behind it?
You may want to look at
- (void)bringSubviewToFront:(UIView *)view
in the parent view.
#implementation UIView (moveToFront)
- (void) moveToFront { [[self superview] bringSubviewToFront:self]; }
#end
I have a UIView which has a bunch of subviews. The subviews should be able to receive touch events, but for some reason the parent UIView takes the touch and does not pass it on. I have created it in a very standard way like I always create views:
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0,0,1024,768)];
self.mainView = myView;
[myView release];
[self.view addSubview:self.mainView];
Then I create subviews and add them as normal:
[self.mainView addSubview:someOtherView];
I know self.mainView is getting the touch events when I listen in the main UIWindow:
VIEW: <UIView: 0x8d34aa0; frame = (0 0; 1024 768);
But why in the world can I not get the subviews to receive touches? I don't understand why this happens sometimes. I am not changing any default properties of mainView.
Had to do with the frame of the parent view. If the frame doesn't enclose the subviews they won't receive touches.
UIView touches do not get passed to subviews if they lie outside of the superview's bounds as mentioned by the solution.
However if you want these subviews to respond to touch, override hitTest:withEvent: of the superview.
Documentation on Event Delivery
Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.
Create a subclass of UIView (or other UIView subclass).
Override hitTest:withEvent.
Use this UIView subclass as the superview, so subview can respond to touches.
Add method below in subclass:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSEnumerator *reverseE = [self.subviews reverseObjectEnumerator];
UIView *iSubView;
while ((iSubView = [reverseE nextObject])) {
UIView *viewWasHit = [iSubView hitTest:[self convertPoint:point toView:iSubView] withEvent:event];
if(viewWasHit) {
return viewWasHit;
}
}
return [super hitTest:point withEvent:event];
}
Note: Reverse enumerator used since subviews are ordered from back to front and we want to test the front most view first.
Does you touch handlers call the superclass handlers?
e.g. in your touchesBegan, calling:
[super touchesBegan:touches withEvent:event];
We're working on a project using some custom views. We have the following hierarchy:
UIViewController -> UIScrollView (custom subclass) -> UIView (custom subclass)
We are presenting a grid of buttons that are dynamically generated. When a user taps one of the UIViews that belong to the custom scroll view we fire a method that looks like this:
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
[[self superview] itemSelected:self];
}
The super view in this case is our custom subclass of UIScrollView. From here we fire another method:
- (void) itemSelected: (id)selectedItem {
itemView *item = selectedItem;
[[self superview] initSliderViewForItemNamed:item.name];
item = nil;
}
Now here is where things break. We want to fire another method in UIViewController to load a new view at the top of our view hierarchy. So in UIViewController we have this method to test for success:
- (void) initSliderViewForItemNamed:(NSString *)selectedItemName {
NSLog(#"Selected item was found! %#",selectedItemName);
}
But we never reach this point and the app crashes. It's because we can't reference the UIViewController here. Instead we're referencing the view property of the UIViewController. So our actual object hierarchy is:
UIViewController.view -> UIScrollView (custom subclass) -> UIView (custom subclass)
This leads me to two questions.
How do we reference the UIViewController from our subview that belongs to the controller's view property?
Is this method convoluted. Is there a better way to do this? Should we be assigning the the UIViewController as a delegate of our custom subclass of UIScrollView?
Jim you should setup a delegate on your custom uiview subclass and let you view controller be it's delegate and conform to the protocol you just created and you will be fine (ie: what a tableview is doing)
Yeh either using a delegate or you implement:-
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
or
(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
depending on how you want to handle the touch.
What I ended up doing to solve this was assign the View Controller as the delegate of the scroll view. It looks like this:
itemScrollView = [[ItemScrollView alloc] initWithFrame:CGRectMake(...)];
[itemScrollView setDelegate:self];
[self.view addSubview: itemScrollView];
Then in my ItemScrollView Implementation I could refer to the ViewController with:
[[self delegate] initSliderViewForItemNamed:selectedItem.name];
Big thanks to everyone who replied. So setting the View Controller as the delegate was the correct answer, however, no one went into detail well enough as to how to do this. So I've covered that here in this response. Additional information can also be found on this question involving delegates:
How do I create delegates in Objective-C?
The idiomatic way to do what you want would be to send the message up the responder chain. UIViewController takes part in the responder chain, so it will receive the message.
In short, I want to detect a touch on the navigation controller titlebar, but having trouble actually catching any touches at all!
Everything is done without IB, if that makes a difference.
My app delegate's .m file contains:
MyViewController *viewController = [[MyViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[window addSubview:navigationController.view];
There are a few other subviews added to this window in a way that overlays navigationController leaving only the navigation bar visible.
MyViewController is a subclass of UIViewController and its .m file contains:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(#"ended\n");
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
NSLog(#"began\n");
}
}
I also tried putting these functions directly into app delegate's .m file, but the console remains blank.
What am I doing wrong?
Well, for lack of a better idea, I added another subview to my app, clear in color, placed programmatically over the navigation bar title and used a custom class for that view with relevant touch methods overridden. Works fine, but the I still wish there was a more elegant solution.
The view controller is inserted into the responder chain between its managed view and the superview:
Because view controllers are tightly bound to the views they manage, they are also part of the responder chain used to handle events. View controllers are themselves descendants of the UIResponder class and are inserted into the responder chain between the managed view and its superview. Thus, if the view managed by a view controller does not handle an event, it passes the event to its view controller, which then has the option of handling the event or forwarding it to the view’s superview.
(the UIViewController documentation)
Is it possible, that the managed view of your controller is eating all the events? What kind of view is it?
Had trouble with this, as my custom view was deeper in the view hierarchy. Instead, I climbed the responder chain until it finds a UIViewController;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Pass to top of chain
UIResponder *responder = self;
while (responder.nextResponder != nil){
responder = responder.nextResponder;
if ([responder isKindOfClass:[UIViewController class]]) {
// Got ViewController
break;
}
}
[responder touchesBegan:touches withEvent:event];
}
try adding the method userInteractionEnabled = YES to your UIImageView
These methods should be put into the UIView subclass not the UIViewControllers...The UIView will receive the touches call backs, then you can make a protocol on the UIView and implement it on the UIViewController so the UIViewController will receive some call back when the touch events occur...Here is a link that talks about protocols and how to define them and implement them Protocols Ref