9

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).

frankish
  • 6,577
  • 8
  • 46
  • 96
  • 4
    This isn't a transparency problem. When animation completes, iOS removes the hidden view controller from the screen. The black you're seeing is the window's background color. I believe iOS7 has some options for this. – Brian Nickel Oct 01 '13 at 21:04
  • @BrianNickel So you mean that the presenter view controller is hidden until presented one is dismissed? In this case, shall I add the view manually and use animation to bring it from the bottom of the screen? – frankish Oct 01 '13 at 21:09
  • 1
    That is correct. You could skip `menuViewController` and just animate `menuView` onto the screen however you like. – Brian Nickel Oct 01 '13 at 21:13
  • Would you please post an answer which mentions the main explanation you've just given? (presenter view controller cannot be seen through presented view controller). So I will mark it as answer and other users can easily find the reason. Thank you! – frankish Oct 01 '13 at 21:17
  • Yes, I believe that's what you'll need to do. Don't present the viewController, instead add it's view as the childView of the presenting view controller's view. Don't forget to add the presented view controller as the childViewController of the presenting view controller. – AC1 Oct 01 '13 at 21:17
  • @AC1 Why should I use self.childViewControllers? To be able to track which views I have added? – frankish Oct 01 '13 at 21:23
  • 1
    No, any "interaction" on the added view is handled by its original view controller. But since it's parent view has changed after it got added to another view, the linkage chain is broken. To properly propagate the method calls (e.g. viewDidAppear) its important to add the child controller as the child too. – AC1 Oct 01 '13 at 21:32

6 Answers6

26

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;
Ric Santos
  • 14,599
  • 6
  • 42
  • 72
  • `modalPresentationStyle` and `transitionDelegate` can be set in `parepareForSegue` –  Jul 07 '14 at 13:08
  • 4
    this should be flagged as best answer – Peter Stajger Oct 21 '14 at 18:32
  • 1
    this breaks in iOS 8. while dismissing the screen is left WHITE – thesummersign Jan 28 '15 at 18:44
  • deleting following lines fixed the issue `[transitionContext.containerView addSubview:toViewController.view]; [transitionContext.containerView addSubview:fromViewController.view];` – thesummersign Jan 28 '15 at 18:51
  • In iOS8 (and iOS7) if you want to fade in/fade out the view controller, change the frame stuff to alpha as per any other animation and change: viewControllerToPresent.modalPresentationStyle to UIModalPresentationPopover. (Don't need to remove the lines as per @geekay for this case). – Brett Feb 06 '15 at 07:23
  • still not working on iOS8 form me, any one with a more explained fix ? – Boaz Saragossi Feb 16 '15 at 21:12
  • @aauser the custom transition is required for iOS 7. – Ric Santos Jun 25 '15 at 03:37
  • 1
    It looks like in iOS8+ the custom transition fails on dismiss because it is not expecting the `presentingViewController`'s view to be moved to `transitionContext.containerView` at any point. When that view is removed, the presenter goes with it. Removing those lines fixes the solution for 8+ (and probably still works on 7). – Brian Nickel Oct 30 '15 at 21:59
8

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.

Brian Nickel
  • 26,143
  • 5
  • 83
  • 109
4

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)
arpwal
  • 865
  • 1
  • 9
  • 17
1

You should use a property modalPresentationStyle available since iOS 3.2.

For example:

presenterViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presenterViewController presentViewController:loginViewController animated:YES completion:NULL];
adnako
  • 1,255
  • 1
  • 20
  • 30
0

Try making top view transparent and add a another view below your desired view and make that view's background color black and set alpha 0.5 or whatever opacity level you like.

Obj-Swift
  • 2,762
  • 3
  • 28
  • 49
0

It's quite an old post and thanks to Ric's answer, it still works well, but few fixes are required to run it on iOS 14. I suppose it works fine on lower versions of iOS, but I hadn't a chance to test it, since my deployment target is iOS 14.

OK, here is an updated solution in Swift:

final class SemiTransparentPopupAnimator: NSObject, UIViewControllerAnimatedTransitioning {
var presenting = false

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return presenting ? 0.4 : 0.2
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        else {
            return
    }
    
    var endFrame = fromVC.view.bounds
    
    if presenting {
        fromVC.view.isUserInteractionEnabled = false
        
        transitionContext.containerView.addSubview(toVC.view)
        
        var startFrame = endFrame
        startFrame.origin.y = endFrame.size.height
        toVC.view.frame = startFrame
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            fromVC.view.tintAdjustmentMode = .dimmed
            toVC.view.frame = endFrame
        } completion: { _ in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    } else {
        toVC.view.isUserInteractionEnabled = true
        
        endFrame.origin.y = endFrame.size.height
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            toVC.view.tintAdjustmentMode = .automatic
            fromVC.view.frame = endFrame
        } completion: { _ in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}
}