diff --git a/PanModal/View/PanContainerView.swift b/PanModal/View/PanContainerView.swift index f5c2892b..06e0175f 100644 --- a/PanModal/View/PanContainerView.swift +++ b/PanModal/View/PanContainerView.swift @@ -15,10 +15,13 @@ import UIKit having to do those changes directly on the view */ class PanContainerView: UIView { + + private weak var presentedViewController: UIViewController? init(presentedView: UIView, frame: CGRect) { super.init(frame: frame) addSubview(presentedView) + presentedViewController = presentedView.parentViewController } @available(*, unavailable) @@ -26,6 +29,22 @@ class PanContainerView: UIView { fatalError("init(coder:) has not been implemented") } + override func accessibilityPerformEscape() -> Bool { + var shouldPerforEscape: Bool = true + if let panModalPresentable = presentedViewController as? PanModalPresentable { + shouldPerforEscape = panModalPresentable.allowsDragToDismiss || panModalPresentable.allowsTapToDismiss + if shouldPerforEscape { + presentedViewController?.dismiss(animated: true, completion: nil) + } + } + return shouldPerforEscape + } +} + +private extension UIResponder { + var parentViewController: UIViewController? { + return next as? UIViewController ?? next?.parentViewController + } } extension UIView { diff --git a/PanModalDemo.xcodeproj/project.pbxproj b/PanModalDemo.xcodeproj/project.pbxproj index 0fe4e141..821e6f80 100644 --- a/PanModalDemo.xcodeproj/project.pbxproj +++ b/PanModalDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0800056627E406A200EF6459 /* UIAccessiblityPerformEscapeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0800056527E406A200EF6459 /* UIAccessiblityPerformEscapeTests.swift */; }; 0F2A2C552239C119003BDB2F /* PanModal.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F2A2C532239C119003BDB2F /* PanModal.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0F2A2C582239C119003BDB2F /* PanModal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2A2C512239C119003BDB2F /* PanModal.framework */; }; 0F2A2C592239C119003BDB2F /* PanModal.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2A2C512239C119003BDB2F /* PanModal.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -90,6 +91,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0800056527E406A200EF6459 /* UIAccessiblityPerformEscapeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAccessiblityPerformEscapeTests.swift; sourceTree = ""; }; 0F2A2C512239C119003BDB2F /* PanModal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PanModal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0F2A2C532239C119003BDB2F /* PanModal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PanModal.h; sourceTree = ""; }; 0F2A2C542239C119003BDB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -207,6 +209,7 @@ 743CABC52226171500634A5A /* Tests */ = { isa = PBXGroup; children = ( + 0800056527E406A200EF6459 /* UIAccessiblityPerformEscapeTests.swift */, 743CABC62226171500634A5A /* PanModalTests.swift */, 743CABC82226171500634A5A /* Info.plist */, ); @@ -552,6 +555,7 @@ buildActionMask = 2147483647; files = ( 743CABC72226171500634A5A /* PanModalTests.swift in Sources */, + 0800056627E406A200EF6459 /* UIAccessiblityPerformEscapeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/UIAccessiblityPerformEscapeTests.swift b/Tests/UIAccessiblityPerformEscapeTests.swift new file mode 100644 index 00000000..49a3143b --- /dev/null +++ b/Tests/UIAccessiblityPerformEscapeTests.swift @@ -0,0 +1,75 @@ +// +// UIAccessiblityPerformEscapeTests.swift +// PanModalTests +// +// Created by Sungdoo on 2022/03/17. +// Copyright © 2022 Detail. All rights reserved. +// + +import XCTest +import PanModal + +class UIAccessiblityPerformEscapeTests: XCTestCase { + + class MockViewController: UIViewController, PanModalPresentable { + var panScrollable: UIScrollView? { nil } + } + + class UnDismissablePanModalViewController: UIViewController, PanModalPresentable { + var panScrollable: UIScrollView? { nil } + var allowsTapToDismiss: Bool { false } + var allowsDragToDismiss: Bool { false } + } + + func testAccessibilityPerformEscape() throws { + + let presenterViewController = UIApplication.shared.keyWindow?.rootViewController + let panModal: UIViewController & PanModalPresentable = MockViewController() + + presenterViewController?.presentPanModal(panModal) + XCTAssertNotNil(presenterViewController?.presentedViewController, "panModal should have been presented") + + let presentDidFisnish = XCTestExpectation() + let dismissDidFinish = XCTestExpectation() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + + let panContainerView = panModal.view.superview + presentDidFisnish.fulfill() + + panContainerView?.accessibilityPerformEscape() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + XCTAssertNil(presenterViewController?.presentedViewController, "panModal should have been dismissed") + dismissDidFinish.fulfill() + } + } + + wait(for: [presentDidFisnish, dismissDidFinish], timeout: 10) + } + + func testAccessibilityPerformEscapeOnDismissDisabledModal() throws { + + let presenterViewController = UIApplication.shared.keyWindow?.rootViewController + let panModal: UIViewController & PanModalPresentable = UnDismissablePanModalViewController() + + presenterViewController?.presentPanModal(panModal) + XCTAssertNotNil(presenterViewController?.presentedViewController, "panModal should have been presented") + + let presentDidFisnish = XCTestExpectation() + let dismissDidFinish = XCTestExpectation() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + + let panContainerView = panModal.view.superview + presentDidFisnish.fulfill() + + panContainerView?.accessibilityPerformEscape() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + XCTAssertNotNil(presenterViewController?.presentedViewController, "panModal shouldn't have been dismissed") + dismissDidFinish.fulfill() + } + } + + wait(for: [presentDidFisnish, dismissDidFinish], timeout: 10) + } +}