Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to present a semi-transparent (half-cut) viewcontroller in iOS?

I use the following code to present a viewcontroller. My problem is: After the animation completes, the transparent main background becomes opaque black.

How can I fix this and make it stay as clearColor?

UIViewController *menuViewController=[[UIViewController alloc]init];
   menuViewController.view.backgroundColor=[UIColor clearColor];
   menuViewController.view.tintColor=[UIColor clearColor];
   menuViewController.view.opaque=NO;

UIView *menuView=[[UIView alloc]initWithFrame:CGRectMake(0,[UIScreen mainScreen].bounds.size.height-200,320,200)];
   menuView.backgroundColor=[UIColor redColor];

[menuViewController.view addSubview:menuView];

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

update: I am trying to see the contents of "self" (the presenter viewcontroller's view).

like image 708
frankish Avatar asked Oct 01 '13 20:10

frankish


3 Answers

Update for iOS 15

Apple has introduced a new API, UISheetPresentationController, which makes it rather trivial to achieve a half-sized (.medium()) sheet presentation. If you are after something more custom, then the original answer is what you need.

let vc = UIViewController()
if let sheet = vc.presentationController as? UISheetPresentationController {
    sheet.detents = [.medium()]
}
self.present(vc, animated: true, completion: nil)

Original Answer

You can present a view controller, and still have the original view controller visible underneath, like a form, in iOS 7. To do so, you will need to do two things:

  1. Set the modal presentation style to custom:

    viewControllerToPresent.modalPresentationStyle = UIModalPresentationCustom;
    
  2. Set the transitioning delegate:

    viewControllerToPresent.transitioningDelegate = self;
    

In this case, we have set the delegate to self, but it can be another object. The delegate needs to implement the two required methods of the protocol, possible like so:

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
    semiModalAnimatedTransition.presenting = YES;
    return semiModalAnimatedTransition;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
    return semiModalAnimatedTransition;
}

At this point you may be thinking, where did that SemiModalAnimatedTransition class come from. Well, it is a custom implementation adopted from teehan+lax's blog.

Here is the class's header:

@interface SemiModalAnimatedTransition : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) BOOL presenting;
@end

And the implementation:

#import "SemiModalAnimatedTransition.h"

@implementation SemiModalAnimatedTransition

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
    return self.presenting ? 0.6 : 0.3;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGRect endFrame = fromViewController.view.bounds;
    
    if (self.presenting) {
        fromViewController.view.userInteractionEnabled = NO;
        
        [transitionContext.containerView addSubview:fromViewController.view];
        [transitionContext.containerView addSubview:toViewController.view];
        
        CGRect startFrame = endFrame;
        startFrame.origin.y = endFrame.size.height;
        
        toViewController.view.frame = startFrame;
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
            toViewController.view.frame = endFrame;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
    else {
        toViewController.view.userInteractionEnabled = YES;
        
        [transitionContext.containerView addSubview:toViewController.view];
        [transitionContext.containerView addSubview:fromViewController.view];
        
        endFrame.origin.y = endFrame.size.height;
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
            fromViewController.view.frame = endFrame;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
}

@end

Not the most straightforward solution, but avoids hacks and works well. The custom transition is required because by default iOS will remove the first view controller at the end of the transition.

Update for iOS 8

For iOS 8, once again the landscape has changed. All you need to do is use the new presentation style .OverCurrentContext, ie:

viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverCurrentContext;
like image 190
Ric Santos Avatar answered Oct 02 '22 01:10

Ric Santos


Update

In most cases, you're going to want to follow the guidelines from Ric's answer, below. As he mentions, menuViewController.modalPresentationStyle = .overCurrentContext is the simplest modern way to keep the presenting view controller visible.

I'm preserving this answer because it provided the most direct solution to the OPs problem, where they already had a view managed by the current view controller and were just looking for a way to present it, and because it explains the actual cause problem.


As mentioned in the comments, this isn't a transparency problem (otherwise you would expect the background to become white). When the presentViewController:animated:completion: animation completes, the presenting view controller is actually removed from the visual stack. The black you're seeing is the window's background color.

Since you appear to just be using menuViewController as a host for menuView to simplify the animation, you could consider skipping menuViewController, adding menuView to your existing view controllers view hierarchy, and animate it yourself.

like image 34
Brian Nickel Avatar answered Oct 02 '22 02:10

Brian Nickel


This is a fairly simple problem to solve. Rather than creating a custom view transition, you just need to set the modalPresentationStyle for the view controller being presented. Also, you should set the background color (and alpha value) for the view controller being presented, in the storyboard / via code.

CustomViewController: UIViewController {
    override func viewDidLoad() {
      super.viewDidLoad()
      view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
    }
}

In the IBAction component of presenting view controller -

let vc = storyboard?.instantiateViewControllerWithIdentifier("customViewController") as! CustomViewController
vc.modalPresentationStyle = UIModalPresentationStyle.Custom
presentViewController(vc, animated: true, completion: nil)
like image 28
arpwal Avatar answered Oct 02 '22 01:10

arpwal