Question

How would I go about presenting a "half view" controller over the top of main view controller?

Requirements: - Present a second view controller that slides over the top of main view controller. - Second view controller should only show over half of the main view controller - Main view controller should remain visible behind second view controller (transparent background, not showing black underneath) - Second view controller should animate in with animation similar to modal vertical cover, or iOS 7 custom transition - User can still interact with buttons on main view controller when second view controller is active (i.e. second view controller does not cover the entire main view controller)r - Second view controller has its own complex logic (cannot be a simple view) - Storyboards, Segues, iOS 7 only - iPhone only, not iPad.

I have tried with modal view controller, but this does not allow interaction with main view controller. Can someone provide an example of how to do this with iOS7 custom transition or another method.

Was it helpful?

Solution

One way to do it is to add the "half modal" controller as a child view controller, and animate its view into place. For this example, I created the "half modal" controller in the storyboard with a frame that's half the height of a 4" iPhone screen. You could use more dynamic methods to account for different screen sizes, but this should get you started.

@interface ViewController ()
@property (strong,nonatomic) UIViewController *modal;
@end

@implementation ViewController


- (IBAction)toggleHalfModal:(UIButton *)sender {
    if (self.childViewControllers.count == 0) {
        self.modal = [self.storyboard instantiateViewControllerWithIdentifier:@"HalfModal"];
        [self addChildViewController:self.modal];
        self.modal.view.frame = CGRectMake(0, 568, 320, 284);
        [self.view addSubview:self.modal.view];
        [UIView animateWithDuration:1 animations:^{
            self.modal.view.frame = CGRectMake(0, 284, 320, 284);;
        } completion:^(BOOL finished) {
            [self.modal didMoveToParentViewController:self];
        }];
    }else{
        [UIView animateWithDuration:1 animations:^{
            self.modal.view.frame = CGRectMake(0, 568, 320, 284);
        } completion:^(BOOL finished) {
            [self.modal.view removeFromSuperview];
            [self.modal removeFromParentViewController];
            self.modal = nil;
        }];
    }
}

OTHER TIPS

The new way to show controller in half screen is ios native style controller.sheetPresentationController but this work only for ios 15 and later, for previously versions you need to set .custom modalPresentationStype

Below code snippet help you for both versions

    controller.modalPresentationStyle = .pageSheet
    if #available(iOS 15.0, *) {

        if let sheet = controller.sheetPresentationController {
            sheet.detents = [.medium()]
        }
        
    } else {
        
        controller.modalPresentationStyle = .custom
        controller.transitioningDelegate = self
    }
    self.present(controller, animated: true, completion: nil)

// MARK: - UIViewControllerTransitioningDelegate

  extension CPPdfPreviewVC: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
    PresentationController(presentedViewController: presented, presenting: presenting)
    }
}

and Add Presentation controller as given

  class PresentationController: UIPresentationController {


  let blurEffectView: UIVisualEffectView!
  var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
  
  override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
      let blurEffect = UIBlurEffect(style: .dark)
      blurEffectView = UIVisualEffectView(effect: blurEffect)
      super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
      tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
      blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
      self.blurEffectView.isUserInteractionEnabled = true
      self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
  }
  
  override var frameOfPresentedViewInContainerView: CGRect {
      CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * 0.4),
             size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height *
              0.6))
  }

  override func presentationTransitionWillBegin() {
      self.blurEffectView.alpha = 0
      self.containerView?.addSubview(blurEffectView)
      self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
          self.blurEffectView.alpha = 0.7
      }, completion: { (UIViewControllerTransitionCoordinatorContext) in })
  }
  
  override func dismissalTransitionWillBegin() {
      self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
          self.blurEffectView.alpha = 0
      }, completion: { (UIViewControllerTransitionCoordinatorContext) in
          self.blurEffectView.removeFromSuperview()
      })
  }
  
  override func containerViewWillLayoutSubviews() {
      super.containerViewWillLayoutSubviews()
    presentedView!.roundCorners([.topLeft, .topRight], radius: 22)
  }

  override func containerViewDidLayoutSubviews() {
      super.containerViewDidLayoutSubviews()
      presentedView?.frame = frameOfPresentedViewInContainerView
      blurEffectView.frame = containerView!.bounds
  }

  @objc func dismissController(){
      self.presentedViewController.dismiss(animated: true, completion: nil)
  }
}

extension UIView {
  func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
      let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners,
                              cornerRadii: CGSize(width: radius, height: radius))
      let mask = CAShapeLayer()
      mask.path = path.cgPath
      layer.mask = mask
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top