检测表在 iOS 13 上被取消
Posted
技术标签:
【中文标题】检测表在 iOS 13 上被取消【英文标题】:Detecting sheet was dismissed on iOS 13 【发布时间】:2019-10-27 08:49:00 【问题描述】:在 ios 13 之前,呈现用于覆盖整个屏幕的视图控制器。并且,当关闭时,父视图控制器viewDidAppear
函数被执行。
现在 iOS 13 将默认将视图控制器呈现为工作表,这意味着卡片将部分覆盖底层视图控制器,这意味着 viewDidAppear
将不会被调用,因为父视图控制器从未真正消失过。
有没有办法检测呈现的视图控制器工作表已被解除?我可以在父视图控制器中覆盖其他一些功能而不是使用某种委托?
【问题讨论】:
在developer.apple.com/videos/play/wwdc2019/224中讨论得很好 那么有没有办法一次性将所有模态表解散到根vc? ***.com/questions/56435510/… 为什么需要知道它何时被解雇?如果要重新加载数据和更新 UI,Notifications 或 KVO 可能是一个不错的选择。 【参考方案1】:有没有办法检测呈现的视图控制器工作表已被解除?
是的。
我可以在父视图控制器中覆盖其他一些功能,而不是使用某种委托?
没有。 “某种代表”就是你的做法。让自己成为演示控制器的代表并覆盖 presentationControllerDidDismiss(_:)
。
https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss
缺少一个通用的运行时生成的事件来通知您呈现的视图控制器(无论是否全屏)已被解除,这确实很麻烦;但这不是一个新问题,因为一直存在非全屏呈现的视图控制器。只是现在(在 iOS 13 中)还有更多!我在别处专门针对此主题单独问答:Unified UIViewController "became frontmost" detection?。
【讨论】:
这还不够。如果您在呈现的 VC 中有一个 nabber 和一个以编程方式关闭您的视图的自定义栏按钮,则表示控制器确实关闭不会被调用。 嗨@Irina - 如果您以编程方式关闭视图,则不需要回调,因为您以编程方式关闭视图 - 你知道你这样做是因为你 做到了。委托方法仅在 user 这样做的情况下使用。 @matt 感谢您的回答。当视图以编程方式被关闭时,它不会被调用(正如 Irina 所说),你是对的,我们知道我们做到了。我只是认为,为了在 iOS13 中使用新的模态演示样式获得一种“viewWillAppear”,编写了不必要的样板代码。当您通过提取路由的架构(例如在 MVVM + 协调器中,或 VIPER 中的路由器类型)管理路由时,它会变得特别混乱 @AdamWaite 我同意,但这个问题并不新鲜。多年来,我们一直遇到这个问题,弹出窗口、非全屏显示的视图控制器、警报等等。我认为这是苹果“事件”目录中的一个严重缺陷。我只是说现实是什么以及为什么。我在这里直接解决这个问题:***.com/questions/54602662/… presentationControllerDidDismiss(_:)。当我单击子 VC 中的后退按钮时未调用。有什么帮助吗?【参考方案2】:这是一个父视图控制器的代码示例,当它作为工作表呈现的子视图控制器(即以默认的 iOS 13 方式)被关闭时,会收到通知:
public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
// This is assuming that the segue is a storyboard segue;
// if you're manually presenting, just set the delegate there.
public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "mySegue"
segue.destination.presentationController?.delegate = self;
public func presentationControllerDidDismiss(
_ presentationController: UIPresentationController)
// Only called when the sheet is dismissed by DRAGGING.
// You'll need something extra if you call .dismiss() on the child.
// (I found that overriding dismiss in the child and calling
// presentationController.delegate?.presentationControllerDidDismiss
// works well).
Jerland2 的回答令人困惑,因为 (a) 最初的提问者希望在工作表dismissed 时获得函数调用(而他实现了presentationControllerDidAttemptToDismiss,当用户尝试和未能关闭工作表),并且(b)设置 isModalInPresentation 是完全正交的,实际上会使呈现的工作表不可关闭(这与 OP 想要的相反)。
【讨论】:
这很好用。只是一个提示,如果您在调用的 VC 上使用导航控制器,您应该将导航控制器分配为presentationController?,委托(而不是导航具有作为 topViewController 的 VC)。 @instAustralia 您能否解释原因或参考文档?谢谢。 presentationControllerDidDismiss 如何在用户按下返回按钮时调用它? @AhmedOsama - 导航控制器是表示控制器,因此是代表,因为它将是响应解雇的人。我也尝试过嵌入在导航控制器中的 VC,但这是我要关闭的实际按钮存在并做出响应的地方。我无法直接在 Apple 文档中找到它,但这里引用了它 sarunw.com/posts/modality-changes-in-ios13【参考方案3】:设置了返回viewWillAppear
和viewDidAppear
的另一个选项
let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen
此选项覆盖全屏,关闭后调用上述方法
【讨论】:
谢谢 PiterPan。这是有效的。这是一个很棒且最快的解决方案。 感谢您以这种快速可靠的方式恢复以前的默认行为。能够立即进行此修复,然后以合理的方式计划向新行为的过渡,真是太好了。 这是一种解决方法而不是修复方法。只是恢复到 iOS 12 样式表对每个人来说都不是很好。 iOS 13 很酷! :) 在 iPad 上使用它时要小心,因为 iPad 在模态显示时默认显示为 pageSheet。这将强制 iPad 显示为全屏 不适合我。我打开模态控制器。用解雇关闭它,但不会调用 willAppear 。为什么?谢谢【参考方案4】:对于未来的读者,这里有一个更完整的实施答案:
-
在根视图控制器准备 segue 添加以下内容(假设您的模态有一个导航控制器)
// Modal Dismiss iOS 13
modalNavController.presentationController?.delegate = modalVc
-
在模态视图控制器中添加以下委托 + 方法
// MARK: - iOS 13 Modal (Swipe to Dismiss)
extension ModalViewController: UIAdaptivePresentationControllerDelegate
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController)
print("slide to dismiss stopped")
self.dismiss(animated: true, completion: nil)
-
确保在模态视图控制器中以下属性为真,以便调用委托方法
self.isModalInPresentation = true
-
利润
【讨论】:
self.isModalInPresentation = true 然后拖动关闭不起作用。删除该行委托方法仍然可以调用。谢谢。 这很困惑,因为 (a) 原始提问者希望在关闭工作表时获得函数调用(而您已经实现了presentationControllerDidAttemptToDismiss,当用户尝试关闭工作表但失败时调用它) 和 (b) 设置 isModalInPresentation 是完全正交的,实际上会使呈现的工作表不可关闭(这与 OP 想要的相反)。 跟进@Matt 的回答点(a):使用presentationControllerDidDismiss
应该可以工作
不太正确,因为presentationControllerDidAttemptToDismiss
适用于用户尝试关闭但被编程阻止的情况(请仔细阅读该方法的文档)。 presentationControllerWillDismiss
方法是检测用户的解雇意图或presentationControllerShouldDismiss
控制解雇或presentationControllerDidDismiss
检测被解雇事实的方法【参考方案5】:
斯威夫特
在iOS13中调用viewWillAppear
的一般解决方案
class ViewController: UIViewController
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
print("viewWillAppear")
//Show new viewController
@IBAction func show(_ sender: Any)
let newViewController = NewViewController()
//set delegate of UIAdaptivePresentationControllerDelegate to self
newViewController.presentationController?.delegate = self
present(newViewController, animated: true, completion: nil)
extension UIViewController: UIAdaptivePresentationControllerDelegate
public func presentationControllerDidDismiss( _ presentationController: UIPresentationController)
if #available(iOS 13, *)
//Call viewWillAppear only in iOS 13
viewWillAppear(true)
【讨论】:
这仅处理使用从顶部滑动的解除,而不是通过调用函数dismiss(_)
。【参考方案6】:
在被解除的UIViewController
上覆盖viewWillDisappear
。它会通过 isBeingDismissed
布尔标志提醒您解雇。
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
if isBeingDismissed
print("user is dismissing the vc")
** 如果用户在向下滑动一半的过程中再次向上滑动卡片,即使卡片没有被关闭,它仍然会注册为被关闭。但这是你可能不关心的边缘情况。
【讨论】:
self.dismiss(animated: Bool, completion: (() -> Void)?)
怎么样
self.dismiss(animated: Bool, completion: (() -> Void)?)
不会检测到解雇。相反,它会导致一个动作发生,然后你就捎带它去做一些工作。使用viewWillDisappear
将监听解雇事件。【参考方案7】:
如果您想在用户从该工作表中关闭模式工作表时执行某些操作。
假设您已经有一些带有@IBAction
的关闭按钮和一个在关闭或执行其他操作之前显示警报的逻辑。您只想检测用户按下此类控制器的时刻。
方法如下:
class MyModalSheetViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
self.presentationController?.delegate = self
@IBAction func closeAction(_ sender: Any)
// your logic to decide to close or not, when to close, etc.
extension MyModalSheetViewController: UIAdaptivePresentationControllerDelegate
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool
return false // <-prevents the modal sheet from being closed
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController)
closeAction(self) // <- called after the modal sheet was prevented from being closed and leads to your own logic
【讨论】:
如果您的模态视图控制器嵌入在导航控制器中,您可能需要调用self.navigationController?.presentationController?.delegate = self
【参考方案8】:
DRAG OR CALL DISMISS FUNC 将使用以下代码。
1) 在根视图控制器中,您可以通过下面的代码告诉它哪个是其表示视图控制器
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "presenterID"
let navigationController = segue.destination as! UINavigationController
if #available(iOS 13.0, *)
let controller = navigationController.topViewController as! presentationviewcontroller
// Modal Dismiss iOS 13
controller.presentationController?.delegate = self
else
// Fallback on earlier versions
navigationController.presentationController?.delegate = self
2) 再次在根视图控制器中,你告诉当它的展示视图控制器被释放时你会做什么
public func presentationControllerDidDismiss(
_ presentationController: UIPresentationController)
print("presentationControllerDidDismiss")
1) 在演示视图控制器中,当您点击这张图片中的取消或保存按钮时。下面的代码将被调用。
self.dismiss(animated: true)
self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!)
【讨论】:
是否需要将navigationController.topViewController 转换为presentationViewController?我发现不是 从取消按钮子 VC 中解散后,如何在父 VC 中重新加载数据?【参考方案9】:在 SwiftUI 中你可以使用 onDismiss 闭包
func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View
【讨论】:
【参考方案10】:如果有人无权访问呈现的视图控制器,他们可以在呈现视图控制器中覆盖以下方法并将modalPresentationStyle
更改为fullScreen
,或者可以使用此方法添加上述策略之一
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil)
if let _ = viewControllerToPresent as? TargetVC
viewControllerToPresent.modalPresentationStyle = .fullScreen
super.present(viewControllerToPresent, animated: flag, completion: completion)
如果呈现的视图控制器是导航控制器并且您想检查根控制器,可以将上述条件更改为类似
if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC
viewControllerToPresent.modalPresentationStyle = .fullScreen
【讨论】:
【参考方案11】:如果您在 FullScreen 中使用 ModalPresentationStyle,则控制器的行为将恢复正常。
ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController("ConsultarController") as ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController(controllerConsultar, true);
【讨论】:
重复现有答案。【参考方案12】:在我看来,苹果不应该将pageSheet
设置为默认modalPresentationStyle
我想使用 swizzling
将 fullScreen
样式恢复为默认样式
像这样:
private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector)
if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
extension UIViewController
static func preventPageSheetPresentationStyle ()
UIViewController.preventPageSheetPresentation
static let preventPageSheetPresentation: Void =
if #available(iOS 13, *)
_swizzling(forClass: UIViewController.self,
originalSelector: #selector(present(_: animated: completion:)),
swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
()
@available(iOS 13.0, *)
private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)? = nil)
if viewControllerToPresent.modalPresentationStyle == .pageSheet
|| viewControllerToPresent.modalPresentationStyle == .automatic
viewControllerToPresent.modalPresentationStyle = .fullScreen
_swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
然后把这行写到你的AppDelegate
UIViewController.preventPageSheetPresentationStyle()
【讨论】:
这很巧妙,但我不能同意。它很老套,更重要的是,它违背了 iOS 13 的特点。您应该在 iOS 13 中使用“卡片”演示文稿。Apple 对我们的期望不是“解决它” ";这是“克服它”。 同意你的观点,这个解决方案无助于使用 Apple 鼓励我们的卡片展示风格。但是,将其设置为默认样式会使现有代码行在某处出错,因为presentingViewController
不会触发viewWillAppear
是的,但正如我在自己的回答中已经说过的那样,对于非全屏演示(例如 iPad 上的弹出框和页面/表单)来说,始终是一个问题,所以这不是什么新鲜事。只是现在还有更多。依赖viewWillAppear
在某种意义上 总是错误的。当然,我不喜欢 Apple 出现并从我身下挖出地板。但正如我所说,我们只需要接受它并以新的方式做事。
在我的项目中,有一些场景我不知道视图控制器(称为presentedController
)出现在哪里,也不知道presentingViewController
到底是什么。例如:在某些情况下,我必须使用UIViewController.topMostViewController()
,它会返回当前窗口上最顶层的视图控制器。这就是为什么我想在我的视图控制器的viewWillAppear
中保持当前行为以做正确的事情(刷新数据、UI)。如果您对解决该问题有任何想法,请提供帮助。
好吧,我相信我在答案末尾链接到的解决方案确实可以解决这个问题。在演示时进行配置需要一些工作,但基本上它可以保证每个演示者(包括警报演示者)在所呈现的视图控制器被解除时听到。【参考方案13】:
调用presentingViewController.viewWillAppear不是很简单吗? 解雇前?
self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)
【讨论】:
这不是你的电话。以上是关于检测表在 iOS 13 上被取消的主要内容,如果未能解决你的问题,请参考以下文章
UISearchController 中的取消按钮在 iOS 13 中没有正确消失
在 iOS 7 中取消 UITableViewCell 滑动删除时收到通知
MFMailComposeViewController 操作表在 iOS5 上 setStatusBarHidden:YES 时不在屏幕上