iOS - 关闭在其视图外触摸的呈现视图控制器
iOS - Dismiss presented view controller touching outside its View
我有一个 CustomPresentationController
可以使用自定义动画进出动画;
这个特定的控制器被显示出来,在屏幕尺寸的 50% 处显示,当我显示它时,我向 presentingViewController
添加了一个阴影灰色视图,因此它增加了一些深度。
如果我点击 NavBar
中的 cancel
按钮,我只能关闭 presentedViewController
,我称之为默认 dismiss(:)
方法。
我想要完成的是检测 presentedViewController
之外的点击,也许在灰色区域之内,所以我可以忽略 presentedViewController
,有点像忽略 ActionSheet
但我没能做到。让我解释一下我到目前为止所做的尝试。
我试图将 UITapGestureRecognizer
添加到阴影灰色视图,但由于我呈现的是不同的控制器,应用引擎可能认为因为阴影视图不在顶层视图中它可能无法访问,所以它 'blocks' 识别器 - 每当我点击它时,手势手柄都不会触发。
我现在正在另外实现向下滑动以关闭,我可以很容易地做到这一点,但我真的希望 tap-outside 功能也能正常工作。
关于如何解决这个问题的任何提示?
应用图片如下:
UITapGestureRecognizer
你的方向是正确的。只要确保你这样实现 shouldRecognizeSimultaneouslyWith
:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
这应该能让手势正确触发。
试试我下面的代码:
您需要在您作为弹出窗口使用的控制器中实现此方法。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Here write down you logic to dismiss controller
}
希望这会奏效。 :D
我只需要在我的一个应用程序中实现它。
我通过添加一个覆盖整个视图的按钮使其工作,一旦点击此按钮就会触发 VC 被关闭。
添加按钮后,您可以在顶部添加自定义视图。
到目前为止,它看起来运行良好。
下面是我的代码(我以编程方式执行所有操作,没有故事板)
//—————————————————————————————
// MARK: View Life Cycle
//—————————————————————————————
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear //VC view background transparent
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Animate blackView opacity to 1 to give some depth
UIView.animate(withDuration: 0.4, delay: 0.2, options: .curveEaseInOut, animations: {
self.blackView.alpha = 1
})
}
//————————————————
// MARK: Setup UI
//————————————————
let blackView: UIView = {
let view = UIView()
view.alpha = 0.0
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}()
//Invisible button which covers the entire view that can be tapped
lazy var dismissLayerBtn: UIButton = {
let btn = UIButton()
btn.addTarget(self, action: #selector(tapToDismiss), for: .touchUpInside)
return btn
}()
@objc func tapToDismiss() {
print("tapToDimiss")
self.dismiss(animated: true, completion: nil)
}
let milestonePickerView: MilestonePickerView = {
let view = MilestonePickerView(frame: .zero)
return view
}()
func setupUI() {
view.addSubview(blackView)
view.addSubview(dismissLayerBtn)
view.addSubview(milestonePickerView) //Important to add the customView after the button.
blackView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
dismissLayerBtn.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
milestonePickerView.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 40, paddingRight: 20, width: 0, height: 400)
//I'm using a custom extension to setup constraints (anchors)
}
如果您使用故事板,请确保将不可见按钮放在自定义视图下。
希望对您有所帮助。
我的解决方案:
在呈现视图控制器(又名 ViewControllerA)时:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcb = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB // ViewControllerB is the presented view controller
vcb.modalPresentationStyle = .custom
vcb.transitioningDelegate = self
modalRatio = Float(0.5) // modalRatio is an object property
self.present(pvc, animated: true)
ViewControllerA 还应实现 Transitioning 委托:
extension ViewControllerA: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PartialSizePresentController(presentedViewController: presented, presenting: presenting, withRatio: modalRatio ?? 0.5) // modal ratio is configurable using modalRatio property
}
}
然后,实现演示控制器(又名 PartialSizePresentController),以便它也处理点击手势:
class PartialSizePresentController: UIPresentationController {
let heightRatio : CGFloat
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, withRatio ratio: Float = 0.5) {
heightRatio = CGFloat(ratio)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let cv = containerView else { fatalError("No container view available") }
return CGRect(x: 0, y: cv.bounds.height * (1 - heightRatio), width: cv.bounds.width, height: cv.bounds.height * heightRatio)
}
override func presentationTransitionWillBegin() {
let bdView = UIView(frame: containerView!.bounds)
bdView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
containerView?.addSubview(bdView)
bdView.addSubview(presentedView!)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PartialSizePresentController.handleTap(_:)))
bdView.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ sender: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
}
// 对我有用
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
var touch: UITouch? = touches.first
if touch?.view == yourView {
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
}
}
我有一个 CustomPresentationController
可以使用自定义动画进出动画;
这个特定的控制器被显示出来,在屏幕尺寸的 50% 处显示,当我显示它时,我向 presentingViewController
添加了一个阴影灰色视图,因此它增加了一些深度。
如果我点击 NavBar
中的 cancel
按钮,我只能关闭 presentedViewController
,我称之为默认 dismiss(:)
方法。
我想要完成的是检测 presentedViewController
之外的点击,也许在灰色区域之内,所以我可以忽略 presentedViewController
,有点像忽略 ActionSheet
但我没能做到。让我解释一下我到目前为止所做的尝试。
我试图将 UITapGestureRecognizer
添加到阴影灰色视图,但由于我呈现的是不同的控制器,应用引擎可能认为因为阴影视图不在顶层视图中它可能无法访问,所以它 'blocks' 识别器 - 每当我点击它时,手势手柄都不会触发。
我现在正在另外实现向下滑动以关闭,我可以很容易地做到这一点,但我真的希望 tap-outside 功能也能正常工作。
关于如何解决这个问题的任何提示?
应用图片如下:
UITapGestureRecognizer
你的方向是正确的。只要确保你这样实现 shouldRecognizeSimultaneouslyWith
:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
这应该能让手势正确触发。
试试我下面的代码:
您需要在您作为弹出窗口使用的控制器中实现此方法。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Here write down you logic to dismiss controller
}
希望这会奏效。 :D
我只需要在我的一个应用程序中实现它。
我通过添加一个覆盖整个视图的按钮使其工作,一旦点击此按钮就会触发 VC 被关闭。
添加按钮后,您可以在顶部添加自定义视图。
到目前为止,它看起来运行良好。
下面是我的代码(我以编程方式执行所有操作,没有故事板)
//—————————————————————————————
// MARK: View Life Cycle
//—————————————————————————————
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear //VC view background transparent
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Animate blackView opacity to 1 to give some depth
UIView.animate(withDuration: 0.4, delay: 0.2, options: .curveEaseInOut, animations: {
self.blackView.alpha = 1
})
}
//————————————————
// MARK: Setup UI
//————————————————
let blackView: UIView = {
let view = UIView()
view.alpha = 0.0
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}()
//Invisible button which covers the entire view that can be tapped
lazy var dismissLayerBtn: UIButton = {
let btn = UIButton()
btn.addTarget(self, action: #selector(tapToDismiss), for: .touchUpInside)
return btn
}()
@objc func tapToDismiss() {
print("tapToDimiss")
self.dismiss(animated: true, completion: nil)
}
let milestonePickerView: MilestonePickerView = {
let view = MilestonePickerView(frame: .zero)
return view
}()
func setupUI() {
view.addSubview(blackView)
view.addSubview(dismissLayerBtn)
view.addSubview(milestonePickerView) //Important to add the customView after the button.
blackView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
dismissLayerBtn.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
milestonePickerView.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 40, paddingRight: 20, width: 0, height: 400)
//I'm using a custom extension to setup constraints (anchors)
}
如果您使用故事板,请确保将不可见按钮放在自定义视图下。
希望对您有所帮助。
我的解决方案:
在呈现视图控制器(又名 ViewControllerA)时:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcb = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB // ViewControllerB is the presented view controller
vcb.modalPresentationStyle = .custom
vcb.transitioningDelegate = self
modalRatio = Float(0.5) // modalRatio is an object property
self.present(pvc, animated: true)
ViewControllerA 还应实现 Transitioning 委托:
extension ViewControllerA: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PartialSizePresentController(presentedViewController: presented, presenting: presenting, withRatio: modalRatio ?? 0.5) // modal ratio is configurable using modalRatio property
}
}
然后,实现演示控制器(又名 PartialSizePresentController),以便它也处理点击手势:
class PartialSizePresentController: UIPresentationController {
let heightRatio : CGFloat
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, withRatio ratio: Float = 0.5) {
heightRatio = CGFloat(ratio)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let cv = containerView else { fatalError("No container view available") }
return CGRect(x: 0, y: cv.bounds.height * (1 - heightRatio), width: cv.bounds.width, height: cv.bounds.height * heightRatio)
}
override func presentationTransitionWillBegin() {
let bdView = UIView(frame: containerView!.bounds)
bdView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
containerView?.addSubview(bdView)
bdView.addSubview(presentedView!)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PartialSizePresentController.handleTap(_:)))
bdView.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ sender: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
}
// 对我有用
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
var touch: UITouch? = touches.first
if touch?.view == yourView {
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
}
}