Show modal view controller with custom frame in iPad
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 co开发者_C百科ntrollers? Is it possible to solve this problem by using form sheets or popovers?
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.
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).
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];
}
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;
}
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
.
精彩评论