Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to dismiss a modal view controller presented as "Form Sheet" when a touch occur outside the form sheet?

Tags:

I have a View Controller (with a UIWebView) i present in Form Sheet style. I have to put a "Done" button in the UIToolbar of the view in the View Controller to have it dismissed. But, since presenting it in "Form Sheet" style leaves plenty of unused space outside the View Controller's view... I was wandering.. Is there a way to detect a touch outside the View? in that "grayed out" area? Thanks in advance

like image 863
mndhkr Avatar asked Feb 09 '11 15:02

mndhkr


People also ask

How do you dismiss a modal view controller?

According to the View Controller Programming guide for iPhone OS, this is incorrect when it comes to dismissing modal view controllers you should use delegation. So before presenting your modal view make yourself the delegate and then call the delegate from the modal view controller to dismiss.

How do I present a view controller from another view controller?

Start a segue from any object that implements an action method, such as a control or gesture recognizer. You may also start segues from table rows and collection view cells. Right-click the control or object in your current view controller. Drag the cursor to the view controller you want to present.


1 Answers

On iOS 4.2, the view hierarchy of a Navigation Controller based app with a modal form sheet is as follows during viewWillAppear:. Once the modal view appears, the UITransitionView has been replaced by a UIDropShadowView that contains the modal view hierarchy.

UIWindow
    UILayoutContainerView (regular hierarchy)
    UIDimmingView 
    UITransitionView

Apple warn against relying on the subviews of built in views, as these are considered private and may change in future releases. This could potentially break your app.

Approach 1
We can insert a transparent view between the last two subviews of window, and have it respond to touch events by dismissing the modal form.

This implementation checks that the window's view hierarchy is as expected, and will silently do nothing if not.

Create and use DismissingView from viewWillAppear: in the modal view controller.

DismissingView *dismiss = [[DismissingView alloc] initWithFrame:window.frame 
                            selector:@selector(dismissView:) target:self];
[dismiss addToWindow:window];
[dismiss release];

And the implementation.

@interface DismissingView : UIView {
}

@property (nonatomic, retain) id target;
@property (nonatomic) SEL selector;

- (id) initWithFrame:(CGRect)frame target:(id)target selector:(SEL)selector;

@end
@implementation DismissingView

@synthesize target;
@synthesize selector;

- (id) initWithFrame:(CGRect)frame target:(id)target selector:(SEL)selector
{
    self = [super initWithFrame:frame];

    self.opaque = NO;
    self.backgroundColor = [UIColor clearColor];
    self.selector = selector;
    self.target = target;

    return self;
}

- (void) addToWindow:(UIWindow*)window
{
    NSUInteger count = window.subviews.count;
    id v = [window.subviews objectAtIndex:count - 1];
    if (![@"UITransitionView" isEqual:NSStringFromClass([v class])]) return;
    v = [window.subviews objectAtIndex:count - 2];
    if (![@"UIDimmingView" isEqual:NSStringFromClass([v class])]) return;

    UIView *front = [window.subviews lastObject];
    [window addSubview:self];
    [window bringSubviewToFront:front];
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    [self removeFromSuperview];
    [target performSelector:selector withObject:self];
}

@end

Approach 2
The first approach is preferred, as this one has additional logic for every touch event on the modal form.

Add the transparent view as the front view of the window. Touch events within the modal view's frame are passed to it, and those outside the frame dismiss the modal form.

Before it appears, the modal view hierarchy looks like this. When it appears, the UIDropShadowView replaces the UITransitionView in the window's subviews. The UILayoutContainerView

UIDropShadowView
    UIImageView
    UILayoutContainerView
        UINavigationTransitionView
            UIViewControllerWrapperView
                UIView (the modal view)
        UINavigationBar

The implementation is mostly the same as before, with a new property and addToWindow: replaced by addToWindow:modalView:.

DismissingView can still be added to the window in viewWillAppear:, since the UIDropShadowView replaces the UITransitionView.

Again we silently do nothing if the view hierarchy is not as expected.

@property (nonatomic, retain) UIView *view;

- (void) addToWindow:(UIWindow*)window modalView:(UIView*)v
{
    while (![@"UILayoutContainerView" isEqual:NSStringFromClass([v class])]) {
        v = v.superview;
        if (!v) {
            return;
        }
    }

    self.view = v;
    [window addSubview:self];
}

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    CGPoint p = [self convertPoint:point toView:view];
    UIView *v = [view hitTest:p withEvent:event];

    return v ? v : [super hitTest:point withEvent:event];
}
like image 166
Lachlan Roche Avatar answered Sep 26 '22 01:09

Lachlan Roche