5

I want to show a modal UIViewController with a custom frame in iPad, centered on top of its parent view controller.

I tried using a form sheet but as far I know the frame and shadow effect can't be changed.

vc.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:cv animated:YES];

I also tried using a popover but as far as I know either I can't center it or I can't hide the arrow.

Is there another way to show modal view controllers? Is it possible to solve this problem by using form sheets or popovers?

hpique
  • 119,096
  • 131
  • 338
  • 476
  • 1
    You can kind of show a popover wherever you like using presentPopoverFromRect and even make it behave modally but only drawback is there's no way to hide the arrow. –  Nov 01 '10 at 03:04

5 Answers5

6

I'm using the following method to create a modal view controller with any given size centered in the screen. You may change it to center in the parent view controller if you want. This is working from storyboard so you maybe can send the view controller as an argument instead. But apart from that it does what you need.

NOTE: I've found that if you try to show another modal over this one it will return to the original size.

- (UIViewController *) presentModalViewControllerWithIdentifier:(NSString *)identifier 
                                                        andSize:(CGSize)size 
                                        andModalTransitionStyle:(UIModalTransitionStyle)modalTransitionStyle {
    UIViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];


    viewController.modalPresentationStyle = UIModalPresentationPageSheet;
    viewController.modalTransitionStyle = modalTransitionStyle;
    [self presentModalViewController:viewController animated:YES];
    viewController.view.superview.autoresizingMask = 
    UIViewAutoresizingFlexibleTopMargin | 
    UIViewAutoresizingFlexibleBottomMargin | 
    UIViewAutoresizingFlexibleLeftMargin | 
    UIViewAutoresizingFlexibleRightMargin;    
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    viewController.view.superview.frame = CGRectMake(0, 0, size.width, size.height);
    CGPoint center = CGPointMake(CGRectGetMidX(screenBounds), CGRectGetMidY(screenBounds));
    viewController.view.superview.center = UIDeviceOrientationIsPortrait(self.interfaceOrientation) ? center : CGPointMake(center.y, center.x);

    return viewController;
}

If it's the only thing you want it works fine. But what I've found is that Apple's implementation of modal view controllers is a little counter productive as you cannot access any of it's underlying parts and so you cannot fully animate them. Useful if you want to have your own transitions.

And it doesn't fall also in the category of a child view controller as it stands over all the other views in the window.

Fábio Oliveira
  • 2,346
  • 21
  • 30
  • +1 I was trying to resize modals for ages, but none of the solutions on StackOverflow were helpful. It either didn't resize/center the View correctly or it didn't work with all transitionStyles. This solution is PERFECT! – Petr Peller Feb 07 '13 at 12:08
  • IOS 7 Update below and it might remove the child spawned vc problem mentioned above – gjpc Oct 14 '13 at 00:17
  • This doesn't work for me. I still have modal with default x and y margins from screen. The height and width I am able to change, but the x and y offset from the main screen is default. – nefarianblack Jul 31 '14 at 09:34
  • @tanmaykhandelwal this is old, before iOS 7 old. For iOS 7 and later check transition delegates and presentation controllers (this one only on iOS 8). – Fábio Oliveira Jul 31 '14 at 12:19
  • @FábioOliveira: transition delegates only take care of transition animations..but not the sizing of ModalViewController. Thanks for the help. – nefarianblack Jul 31 '14 at 17:57
  • I got it how to do it. In my case, changing bounds in `viewWillLayoutSubviews` did the trick. – nefarianblack Jul 31 '14 at 19:10
4

There is no official way to do this however you can get the desired behavior by writing a custom view which keeps a reference or delegate to interact with its presenting view controller and adding it to the view hierarchy. To really get the modal feel you can also place a transparent overlay over the presenting controller just below your 'modal' view. I have done this in a number of apps and it usually works out great. You will likely need to make the custom overlay view so you can intercept touches and more elegantly animate its presentation.

My transparent overlay is usually something like this:

@protocol TransparentOverlayDelegate <NSObject>

@optional
- (void)transparentOverlayWillDismiss:(TransparentOverlay *)backgroundTouch;
- (void)transparentOverlayDidDismiss:(TransparentOverlay *)backgroundTouch;
@end


@interface TransparentOverlay : UIView {

    id<TransparentOverlayDelegate> _delegate;
    UIView *_contentView;
    CGFloat _pAlpha;
}

@property(nonatomic, assign) id<TransparentOverlayDelegate> delegate;
@property(nonatomic, retain) UIView *contentView;
@property(nonatomic, assign) CGFloat pAlpha;

- (void)presentTransparentOverlayInView:(UIView *)view;
- (void)dismissTransparentOverlay:(BOOL)animated;

My custom modal view is usually something like this:

@protocol ModalViewDelegate <NSObject>
- (void)performSelectorOnDelegate:(SEL)selector;
@end

@interface ModalView : UIView {
    id<ModalViewDelegate> _delegate;
}

@property(nonatomic, assign) id<ModalViewDelegate> delegate;

In my presenting view controller I would usually do the following :

- (void)presentModalController {
    TransparentOverlay *to = [[[TransparentOverlay alloc] initWithFrame:self.view.bounds] autorelease];
    to.delegate = self;

    ModalView *mv = [[ModalView alloc] initWithFrame:CGRectMake(500, 500, 300, 300)];
    mv.delegate = self;

    to.contentView = mv;
    [mv release];

    [to presentTransparentOverlayInView:self.view]; 
}

Using the delegates defined on the two classes gives me pretty much open access to manipulate my presenting controller as well as my presentation and dismissal as desired. The only downside to this is when it is used on a view with a NavigationBar, as the bounds of the presenting controller's view will not contain the bounds of the NavigationBar leaving it open for interaction, there are ways to get around this but not of them are very pretty (adding to the navigation controller's view is one option).

RLB
  • 171
  • 1
  • 7
  • What's the purpose of `- (void)performSelectorOnDelegate:(SEL)selector;` in the `ModalViewDelegate`? – Matt H. Jun 21 '12 at 21:20
1

I prefer fabio-oliveira's answer but it requires some adjustment to allow it to work on IOS 7's multi thread environment:

- (UIViewController *)presentModalViewControllerWithId:(NSString *)identifier
                                           andSize:(CGSize)size
                           andModalTransitionStyle:(UIModalTransitionStyle)style
{
    UIViewController *viewController = 
            [self.storyboard instantiateViewControllerWithIdentifier:identifier];

    viewController.modalPresentationStyle = UIModalPresentationPageSheet;
    viewController.modalTransitionStyle = style;

    [self presentViewController:viewController animated:YES completion:nil];

    viewController.view.superview.autoresizingMask = 
        UIViewAutoresizingFlexibleTopMargin    |
        UIViewAutoresizingFlexibleBottomMargin | 
        UIViewAutoresizingFlexibleLeftMargin   | 
        UIViewAutoresizingFlexibleRightMargin;

    viewController.view.superview.frame = 
                   CGRectMake(0, 0, size.width, size.height);

   return viewController;
}

Now place the centering code in the target VC so the new thread does the work:

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGPoint center = CGPointMake(CGRectGetMidX(screenBounds),
                                 CGRectGetMidY(screenBounds));

    self.view.superview.center =
    UIDeviceOrientationIsPortrait(self.interfaceOrientation) ? 
                                       center : CGPointMake(center.y, center.x);
}

Oh Yes, if you are supporting those lovely IOS 5 iPad 1's, be sure to let the target vc rotate:

- (BOOL)shouldAutorotateToInterfaceOrientation:
                         (UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}
gjpc
  • 1,428
  • 14
  • 21
  • hi @gjpc, I used your code and it works fine, but when it pops up,I mean when the modalView is presented , it opens up from corner and it dismisses towards center. Did you find this. – Ranjit Oct 23 '13 at 07:53
  • @Ranjit I use andModalTransitionStyle:UIModalTransitionStyleCoverVertical]; and they appear from the bottom and leave through the bottom – gjpc Oct 24 '13 at 20:30
  • I tried using ModalTransitionStyle:UIModalTransitionStyleCoverVertical];, But the modal appears from bottom corner and when I dismiss it dismisses at center of the bottom. – Ranjit Oct 25 '13 at 06:21
  • Thanks gjpc. This works for me. Non of the above methods did. Your a life saver. – hdsenevi Sep 04 '14 at 15:38
1

In my case, overriding viewWillLayoutSubviews did the trick.

- (void)viewWillLayoutSubviews {
        [super viewWillLayoutSubviews];

    self.view.superview.frame = CGRectMake(100.f, 100.f, <width>, <height>); 
}

Nothing more was needed to change the position and bounds of ModalViewController.

nefarianblack
  • 802
  • 8
  • 16
  • This worked for me, until user activity brought the iPad keyboard onscreen at which point the modal moved back down to its previous position. – Tony Adams Apr 27 '15 at 19:00
1

This may not totally answer your question. But I call the following code from a rightbuttonitem on a navigation controller that I previously placed at the top of a detail view of a splitviewcontroller.

This modal view covers the whole screen. Did you want to cover, for example, the detail view only if called in the detailviewcontroller?

- (void)aboutAction {

About *about = [[About alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc]initWithRootViewController:about];
[about release];
nav1.navigationBar.barStyle = UIBarStyleBlackOpaque;
nav1.modalPresentationStyle = UIModalPresentationFullScreen;
nav1.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:nav1 animated:YES ];
[nav1 release];

}
musefan
  • 47,875
  • 21
  • 135
  • 185
cliff
  • 19
  • 2