如何在半屏幕上呈现ViewController

Uma*_*zal 16 uiviewcontroller ios swift swift3

我有一个从底部UIViewControllerUIView覆盖viewController的1/3.像这样

在此输入图像描述

我想在另一个ViewController上呈现这个viewController.它应该从底部动画出现,它应该忽略到底部动画.

但我不希望它覆盖整个屏幕.显示它的viewController应该在后面可见.

这似乎是一个基本问题但我无法完成它.有人可以指点我的方向吗?

编辑:

这就是我迄今为止所尝试过的.我创建了这些类

// MARK: -

class MyFadeInFadeOutTransitioning: NSObject, UIViewControllerTransitioningDelegate {
var backgroundColorAlpha: CGFloat = 0.5
var shoulDismiss = false

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {

    let fadeInPresentAnimationController = MyFadeInPresentAnimationController()
        fadeInPresentAnimationController.backgroundColorAlpha = backgroundColorAlpha

    return fadeInPresentAnimationController
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {

    let fadeOutDismissAnimationController = MyFadeOutDismissAnimationController()

    return fadeOutDismissAnimationController
}

}

// MARK: -

class MYFadeInPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {

let kPresentationDuration = 0.5
var backgroundColorAlpha: CGFloat?

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return kPresentationDuration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!

    toViewController.view.backgroundColor = UIColor.clear

    let toViewFrame = transitionContext.finalFrame(for: toViewController)
    let containerView = transitionContext.containerView

    if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
        let transform = CGAffineTransform(translationX: 0.0, y: pickerContainerView.frame.size.height)
        pickerContainerView.transform = transform
    }

    toViewController.view.frame = toViewFrame
    containerView.addSubview(toViewController.view)

    UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear , animations: {
        toViewController.view.backgroundColor = UIColor(white: 0.0, alpha: self.backgroundColorAlpha!)

        if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
            pickerContainerView.transform = CGAffineTransform.identity
        }

    }) { (finished) in
        transitionContext.completeTransition(true)
    }
}

}

// MARK: -

class MYFadeOutDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let kDismissalDuration = 0.15

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return kDismissalDuration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
    let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
    let containerView = transitionContext.containerView

    containerView.addSubview(toViewController.view)
    containerView.sendSubview(toBack: toViewController.view)

    UIView.animate(withDuration: kDismissalDuration, delay: 0.0, options: .curveLinear, animations: {
        //            fromViewController.view.backgroundColor = UIColor.clearColor()
        //            if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
        //                let transform = CGAffineTransformMakeTranslation(0.0, pickerContainerView.frame.size.height)
        //                pickerContainerView.transform = transform
        //            }
        fromViewController.view.alpha = 0.0

    }) { (finished) in
        let canceled: Bool = transitionContext.transitionWasCancelled
        transitionContext.completeTransition(true)

        if !canceled {
            UIApplication.shared.keyWindow?.addSubview(toViewController.view)
        }
    }
}

}
Run Code Online (Sandbox Code Playgroud)

在正在呈现的viewController中,我正在做如下

var customTransitioningDelegate: MYFadeInFadeOutTransitioning? = MYFadeInFadeOutTransitioning()

    init() {
    super.init(nibName: "SomeNibName", bundle: Bundle.main)
    transitioningDelegate = customTransitioningDelegate
    modalPresentationStyle = .custom

    customTransitioningDelegate?.backgroundColorAlpha = 0.0
} 
Run Code Online (Sandbox Code Playgroud)

它确实呈现了viewController,我也可以看到背景viewController.但我希望它能从底部呈现动画效果.用动画解雇到底部.我怎样才能做到这一点 ?

Sre*_*tan 38

iOS 15 *

使用UISheetPresentationController

如何使用

let yourVC = YourViewController()

if let sheet = yourVC.sheetPresentationController {
    sheet.detents = [.medium()]
}
self.present(yourVC, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

在这里阅读更多内容

iOS 15 以下

使用UIPresentationController,它也适用于 ios 15。但如果您的应用程序仅支持 iOS 15,请使用上面的代码。

import UIKit

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
  }
}
Run Code Online (Sandbox Code Playgroud)

如何使用

将UIViewControllerTransitioningDelegate添加到您呈现的 ViewController

// MARK: - UIViewControllerTransitioningDelegate
extension PresentingViewController: UIViewControllerTransitioningDelegate {
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        PresentationController(presentedViewController: presented, presenting: presenting)
    }
}
Run Code Online (Sandbox Code Playgroud)

在 PresentingViewController 中展示你的 VC

let yourVC = YourViewController()
yourVC.modalPresentationStyle = .custom
yourVC.transitioningDelegate = self
self.present(yourVC, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

代码参考


Dat*_*ris 24

如果你想在半个屏幕上显示一个视图控制器,我建议使用UIPresentationController它,它允许你设置视图控制器的框架.一句忠告,这个方法将停止用户交互,presentingViewController直到你解雇presentedViewController,所以如果你想要显示视图控制器超过一半的屏幕,同时保留用户与presentingViewController你的交互,你应该使用像其他答案建议的容器视图.这是一个UIPresentationController类的示例,它可以执行您想要的操作

import UIKit
class ForgotPasswordPresentationController: UIPresentationController{
    let blurEffectView: UIVisualEffectView!
    var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
    func dismiss(){
        self.presentedViewController.dismiss(animated: true, completion: nil)
    }
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
        blurEffectView = UIVisualEffectView(effect: blurEffect)
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismiss))
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.blurEffectView.isUserInteractionEnabled = true
        self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
    }
    override var frameOfPresentedViewInContainerView: CGRect{
        return CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height/2), size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height/2))
    }
    override func dismissalTransitionWillBegin() {
        self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
            self.blurEffectView.alpha = 0
        }, completion: { (UIViewControllerTransitionCoordinatorContext) in
            self.blurEffectView.removeFromSuperview()
        })
    }
    override func presentationTransitionWillBegin() {
        self.blurEffectView.alpha = 0
        self.containerView?.addSubview(blurEffectView)
        self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
            self.blurEffectView.alpha = 1
        }, completion: { (UIViewControllerTransitionCoordinatorContext) in

        })
    }
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView!.layer.masksToBounds = true
        presentedView!.layer.cornerRadius = 10
    }
    override func containerViewDidLayoutSubviews() {
        super.containerViewDidLayoutSubviews()
        self.presentedView?.frame = frameOfPresentedViewInContainerView
        blurEffectView.frame = containerView!.bounds
    }
}
Run Code Online (Sandbox Code Playgroud)

当您点击presentedViewController框架外部时,这还会添加模糊视图和点按以关闭.您需要设置transitioningDelegatepresentedViewController贯彻

presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?

在那里的方法.不要忘记也设置 modalPresentationStyle = .custom 了presentViewController

我发现UIPresentationController的用法是一种更清洁的方法.祝好运

  • UIPresentationController 不是呈现的东西——它处理呈现。缺少以下步骤: 1 - 实现 UIViewControllerTransitioningDelegate 协议,并在presentationController(forPresented:presenting:source:) 方法中返回 UIPresentationController (例如本例中的 ForgotPasswordPresentationController) 2 - 以通常的方式呈现视图控制器,但进行一些自定义,例如 `myPresentedVC.modalPresentationStyle = .custom myPresetedVC.transitioningDelegate = <步骤 1 中的类> myPresentedVC.modalPresentationStyle = .custom` (3认同)
  • 我现在如何展示它?您的代码下方的说明非常不清楚-请考虑以下事实:您正在为从未使用过UIPresentationController的人员回答问题 (2认同)

reg*_*cob 7

我建议使用容器视图来实现此功能.看看这里参考.

这意味着您可以在另一个ViewController中的UIView中显示嵌入的UIViewController(及其子类).然后你可以为淡入或任何你想要的动画制作动画.


wli*_*xcc 7

感谢 @aheze 和 @Sreekuttan 提供了 iOS15 解决方案

我提供iOS13-15解决方案

iOS15

使用UISheetPresentationController

    let vc = HalfScreenVC()
    if #available(iOS 15.0, *) {
        if let sheet = vc.sheetPresentationController {
            sheet.detents = [.medium()]
            sheet.preferredCornerRadius = 20
        }
    }
    present(vc, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

iOS13-iOS14

 class HalfScreenVC: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .red
        }
        
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            
            if #available(iOS 15.0, *) {
                
            } else {
                // Below iOS 15, change frame here
                self.view.frame = CGRect(x: 0, y: UIScreen.main.bounds.height / 5 * 2, width: self.view.bounds.width, height: UIScreen.main.bounds.height / 5 * 3)
                self.view.layer.cornerRadius = 20
                self.view.layer.masksToBounds = true
            }
        } 

    }
Run Code Online (Sandbox Code Playgroud)

iPhone 13.0 - 15.4 在此输入图像描述 iPad 13.0 - 15.4 在此输入图像描述

如果我们使用文本字段,我们想要全屏输入

    class HalfPresentVC: UIViewController {
        
        let textfield = UITextField(frame: CGRect(x: 20, y: 30, width: 200, height: 40))
        var initialBounds: CGRect?
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .red
            
            textfield.borderStyle = .roundedRect
            textfield.placeholder = "enter text"
            view.addSubview(textfield)
        }
        
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            if #available(iOS 15.0, *) {
               
            } else if initialBounds == nil {
                NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
                NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
                
                initialBounds = view.bounds
                view.layer.cornerRadius = 20
                view.layer.masksToBounds = true
                view.frame = CGRect(x: 0, y: view.bounds.height / 5 * 2, width: view.bounds.width, height: view.bounds.height / 5 * 3)
            }
        }
        
        @objc func keyboardWillShow() {
            guard let initialBounds = initialBounds else {
                return
            }
            view.frame = initialBounds
        }
        
        @objc func keyboardWillHide() {
            guard let initialBounds = initialBounds else {
                return
            }
            view.frame = CGRect(x: 0, y: initialBounds.height / 5 * 2, width: initialBounds.width, height: initialBounds.height / 5 * 3)
        }
        
        deinit {
            NotificationCenter.default.removeObserver(self)
        }

    }
Run Code Online (Sandbox Code Playgroud)

全屏输入


Abd*_*ich 6

有更新的代码来实现此功能。在您想要呈现 ViewController 的操作上

@IBAction func btnShow(_ sender: Any) {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let pvc = storyboard.instantiateViewController(withIdentifier: "SubViewController") as! SubViewController
    pvc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
    self.present(pvc, animated: true, completion: nil)
}
Run Code Online (Sandbox Code Playgroud)

转到 StoryBoard 选择subViewControllerUIView在其中添加一个。

对于模糊效果,将其约束设置为

(top:0,Bottom:0,Leading:0,Trailing:0)
Run Code Online (Sandbox Code Playgroud)

并将其颜色更改blackalpha您想要的颜色。

然后添加另一个UIView选项,将其约束设置为

(top:-,Bottom:0,Leading:0,Trailing:0)
Run Code Online (Sandbox Code Playgroud)

将其height约束设置为equal height with superview(self.View)并将其更改multipler为 0.33 或 0.34。


Suh*_*til 5

您可以使用UIPresentationController来实现这一点。在呈现 ViewController 时实现UIViewControllerTransitioningDelegate方法并从委托方法返回您的 PresentationController

func presentationController(forPresented presented: UIViewController, 
                          presenting: UIViewController?, 
                              source: UIViewController) -> UIPresentationController? 
Run Code Online (Sandbox Code Playgroud)

你可以参考这个有类似要求的答案。或者,您可以按照其他答案中的建议使用 UIView 动画或嵌入式视图控制器。

编辑:

在 Github 中找到的示例项目

https://github.com/martinnormark/HalfModalPresentationController


ahe*_*eze 5

iOS 15(测试版)

有一个新类 ,UISheetPresentationController其中包含一个名为 的属性detents。这使您可以指定所需的行为类型。

半屏单张

class ViewController: UIViewController {
    @IBAction func nextButtonPressed(_ sender: Any) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let viewController = storyboard.instantiateViewController(withIdentifier: "NextViewController")
        
        if let presentationController = viewController.presentationController as? UISheetPresentationController {
            presentationController.detents = [.medium()] /// set here!
        }
        
        self.present(viewController, animated: true)
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

半屏片

半屏和全屏表

只需更改presentationController.detents = [.medium()]为:

presentationController.detents = [.medium(), .large()]
Run Code Online (Sandbox Code Playgroud)

结果:

半屏片