Compare commits

..

No commits in common. "master" and "1.0" have entirely different histories.
master ... 1.0

35 changed files with 249 additions and 1153 deletions

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -35,7 +35,7 @@ Issues labelled `good first contribution`.
For your contribution to be accepted:
- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackHQ/PanModal).
- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/PanModal).
- [x] The test suite must be complete and pass.
- [x] The changes must be approved by code review.
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.

View File

@ -1,22 +0,0 @@
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "PanModal",
platforms: [.iOS(.v10)],
products: [
.library(
name: "PanModal",
targets: ["PanModal"]),
],
dependencies: [],
targets: [
.target(
name: "PanModal",
dependencies: [],
path: "PanModal")
],
swiftLanguageVersions: [.version("5.0")]
)

View File

@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'PanModal'
s.version = '1.3.1'
s.version = '1.0'
s.summary = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
# This description is used to generate tags and improve search results.
@ -17,13 +17,17 @@ Pod::Spec.new do |s|
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/PanModal'
s.description = <<-DESC
PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
DESC
s.homepage = 'https://github.com/slackhq/PanModal'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'slack' => 'opensource@slack.com' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/PanModal.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/slackhq'
s.source = { :git => 'https://github.com/slackhq/PanModal.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/slackhq
s.ios.deployment_target = '10.0'
s.swift_version = '5.0'
s.source_files = 'PanModal/**/*.{swift,h,m}'
s.source_files = 'PanModal/**/*'
end

View File

@ -7,22 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
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, ); }; };
0F2A2C5E2239C137003BDB2F /* PanModalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A2220BA6E500124CE1 /* PanModalAnimator.swift */; };
0F2A2C5F2239C139003BDB2F /* PanModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139066216D9458007A3E64 /* PanModalPresentationAnimator.swift */; };
0F2A2C602239C13C003BDB2F /* PanModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906C216D9458007A3E64 /* PanModalPresentationController.swift */; };
0F2A2C612239C140003BDB2F /* PanModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */; };
0F2A2C622239C148003BDB2F /* PanModalHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A4220BA76D00124CE1 /* PanModalHeight.swift */; };
0F2A2C632239C14B003BDB2F /* PanModalPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139068216D9458007A3E64 /* PanModalPresentable.swift */; };
0F2A2C642239C14E003BDB2F /* PanModalPresentable+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC0EE7B21917F2500208DBC /* PanModalPresentable+Defaults.swift */; };
0F2A2C652239C151003BDB2F /* PanModalPresentable+UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139069216D9458007A3E64 /* PanModalPresentable+UIViewController.swift */; };
0F2A2C662239C153003BDB2F /* PanModalPresentable+LayoutHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A6220BA78800124CE1 /* PanModalPresentable+LayoutHelpers.swift */; };
0F2A2C672239C157003BDB2F /* PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906A216D9458007A3E64 /* PanModalPresenter.swift */; };
0F2A2C682239C15D003BDB2F /* UIViewController+PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A9220BA82A00124CE1 /* UIViewController+PanModalPresenter.swift */; };
0F2A2C692239C162003BDB2F /* DimmedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906E216D9458007A3E64 /* DimmedView.swift */; };
0F2A2C6A2239C165003BDB2F /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
743CABB02225FC9F00634A5A /* UserGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */; };
743CABB22225FD1100634A5A /* UserGroupHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */; };
743CABB42225FE7700634A5A /* UserGroupMemberPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */; };
@ -41,7 +25,6 @@
943904ED2226366700859537 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EC2226366700859537 /* AlertViewController.swift */; };
943904EF2226383700859537 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EE2226383700859537 /* NavigationController.swift */; };
943904F32226484F00859537 /* UserGroupStackedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904F22226484F00859537 /* UserGroupStackedViewController.swift */; };
944EBA2E227BB7F400C4C97B /* FullScreenNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */; };
94795C9B21F0335D008045A0 /* PanModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */; };
94795C9D21F03368008045A0 /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
DC13905E216D90D5007A3E64 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC13905D216D90D5007A3E64 /* Assets.xcassets */; };
@ -59,13 +42,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
0F2A2C562239C119003BDB2F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DCA741A2216D90410021F2F2 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0F2A2C502239C119003BDB2F;
remoteInfo = PanModal;
};
743CABC92226171500634A5A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DCA741A2216D90410021F2F2 /* Project object */;
@ -75,24 +51,7 @@
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
0F2A2C5D2239C119003BDB2F /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
0F2A2C592239C119003BDB2F /* PanModal.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
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 = "<group>"; };
0F2A2C542239C119003BDB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupViewController.swift; sourceTree = "<group>"; };
743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupHeaderView.swift; sourceTree = "<group>"; };
743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupMemberPresentable.swift; sourceTree = "<group>"; };
@ -113,7 +72,6 @@
943904EC2226366700859537 /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
943904EE2226383700859537 /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
943904F22226484F00859537 /* UserGroupStackedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupStackedViewController.swift; sourceTree = "<group>"; };
944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenNavController.swift; sourceTree = "<group>"; };
94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanModalPresentationDelegate.swift; sourceTree = "<group>"; };
94795C9C21F03368008045A0 /* PanContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanContainerView.swift; sourceTree = "<group>"; };
DC13905D216D90D5007A3E64 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -126,20 +84,13 @@
DC13906E216D9458007A3E64 /* DimmedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimmedView.swift; sourceTree = "<group>"; };
DC3B2EB9222A560A000C8A4A /* TransientAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientAlertViewController.swift; sourceTree = "<group>"; };
DC3B2EBD222A58C9000C8A4A /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
DCA741AA216D90410021F2F2 /* PanModalDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PanModalDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
DCA741AA216D90410021F2F2 /* PanModal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PanModal.app; sourceTree = BUILT_PRODUCTS_DIR; };
DCA741AD216D90410021F2F2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
DCA741B9216D90420021F2F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DCC0EE7B21917F2500208DBC /* PanModalPresentable+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PanModalPresentable+Defaults.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0F2A2C4E2239C119003BDB2F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
743CABC12226171500634A5A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -151,22 +102,12 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0F2A2C582239C119003BDB2F /* PanModal.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0F2A2C522239C119003BDB2F /* PanModal */ = {
isa = PBXGroup;
children = (
0F2A2C532239C119003BDB2F /* PanModal.h */,
0F2A2C542239C119003BDB2F /* Info.plist */,
);
path = PanModal;
sourceTree = "<group>";
};
743CABAE2225FC4A00634A5A /* User Groups */ = {
isa = PBXGroup;
children = (
@ -240,22 +181,6 @@
path = Presenter;
sourceTree = "<group>";
};
944EBA2B227BB7D900C4C97B /* Basic */ = {
isa = PBXGroup;
children = (
943904EA2226354100859537 /* BasicViewController.swift */,
);
path = Basic;
sourceTree = "<group>";
};
944EBA2C227BB7E100C4C97B /* Full Screen */ = {
isa = PBXGroup;
children = (
944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */,
);
path = "Full Screen";
sourceTree = "<group>";
};
DC13905F216D93AB007A3E64 /* Resources */ = {
isa = PBXGroup;
children = (
@ -328,8 +253,7 @@
DC139079216D9AAA007A3E64 /* View Controllers */ = {
isa = PBXGroup;
children = (
944EBA2B227BB7D900C4C97B /* Basic */,
944EBA2C227BB7E100C4C97B /* Full Screen */,
943904EA2226354100859537 /* BasicViewController.swift */,
DC3B2EBB222A5882000C8A4A /* Alert */,
DC3B2EBC222A5893000C8A4A /* Alert (Transient) */,
743CB2AB222661EA00665A55 /* User Groups (Stacked) */,
@ -362,7 +286,6 @@
DCA741AC216D90410021F2F2 /* Sample */,
DC139062216D9431007A3E64 /* PanModal */,
743CABC52226171500634A5A /* Tests */,
0F2A2C522239C119003BDB2F /* PanModal */,
DCA741AB216D90410021F2F2 /* Products */,
);
sourceTree = "<group>";
@ -370,9 +293,8 @@
DCA741AB216D90410021F2F2 /* Products */ = {
isa = PBXGroup;
children = (
DCA741AA216D90410021F2F2 /* PanModalDemo.app */,
DCA741AA216D90410021F2F2 /* PanModal.app */,
743CABC42226171500634A5A /* PanModalTests.xctest */,
0F2A2C512239C119003BDB2F /* PanModal.framework */,
);
name = Products;
sourceTree = "<group>";
@ -390,36 +312,7 @@
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
0F2A2C4C2239C119003BDB2F /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
0F2A2C552239C119003BDB2F /* PanModal.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
0F2A2C502239C119003BDB2F /* PanModal */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0F2A2C5A2239C119003BDB2F /* Build configuration list for PBXNativeTarget "PanModal" */;
buildPhases = (
0F2A2C4C2239C119003BDB2F /* Headers */,
0F2A2C4D2239C119003BDB2F /* Sources */,
0F2A2C4E2239C119003BDB2F /* Frameworks */,
0F2A2C4F2239C119003BDB2F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = PanModal;
productName = PanModal;
productReference = 0F2A2C512239C119003BDB2F /* PanModal.framework */;
productType = "com.apple.product-type.framework";
};
743CABC32226171500634A5A /* PanModalTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */;
@ -438,23 +331,21 @@
productReference = 743CABC42226171500634A5A /* PanModalTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
DCA741A9216D90410021F2F2 /* PanModalDemo */ = {
DCA741A9216D90410021F2F2 /* PanModal */ = {
isa = PBXNativeTarget;
buildConfigurationList = DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModalDemo" */;
buildConfigurationList = DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModal" */;
buildPhases = (
DCA741A6216D90410021F2F2 /* Sources */,
DCA741A7216D90410021F2F2 /* Frameworks */,
DCA741A8216D90410021F2F2 /* Resources */,
0F2A2C5D2239C119003BDB2F /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
0F2A2C572239C119003BDB2F /* PBXTargetDependency */,
);
name = PanModalDemo;
name = PanModal;
productName = PanModal;
productReference = DCA741AA216D90410021F2F2 /* PanModalDemo.app */;
productReference = DCA741AA216D90410021F2F2 /* PanModal.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -467,9 +358,6 @@
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = Detail;
TargetAttributes = {
0F2A2C502239C119003BDB2F = {
CreatedOnToolsVersion = 10.1;
};
743CABC32226171500634A5A = {
CreatedOnToolsVersion = 10.1;
TestTargetID = DCA741A9216D90410021F2F2;
@ -479,7 +367,7 @@
};
};
};
buildConfigurationList = DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModalDemo" */;
buildConfigurationList = DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModal" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
@ -492,21 +380,13 @@
projectDirPath = "";
projectRoot = "";
targets = (
DCA741A9216D90410021F2F2 /* PanModalDemo */,
DCA741A9216D90410021F2F2 /* PanModal */,
743CABC32226171500634A5A /* PanModalTests */,
0F2A2C502239C119003BDB2F /* PanModal */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0F2A2C4F2239C119003BDB2F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
743CABC22226171500634A5A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -527,26 +407,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0F2A2C4D2239C119003BDB2F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0F2A2C5E2239C137003BDB2F /* PanModalAnimator.swift in Sources */,
0F2A2C5F2239C139003BDB2F /* PanModalPresentationAnimator.swift in Sources */,
0F2A2C602239C13C003BDB2F /* PanModalPresentationController.swift in Sources */,
0F2A2C612239C140003BDB2F /* PanModalPresentationDelegate.swift in Sources */,
0F2A2C622239C148003BDB2F /* PanModalHeight.swift in Sources */,
0F2A2C632239C14B003BDB2F /* PanModalPresentable.swift in Sources */,
0F2A2C642239C14E003BDB2F /* PanModalPresentable+Defaults.swift in Sources */,
0F2A2C652239C151003BDB2F /* PanModalPresentable+UIViewController.swift in Sources */,
0F2A2C662239C153003BDB2F /* PanModalPresentable+LayoutHelpers.swift in Sources */,
0F2A2C672239C157003BDB2F /* PanModalPresenter.swift in Sources */,
0F2A2C682239C15D003BDB2F /* UIViewController+PanModalPresenter.swift in Sources */,
0F2A2C692239C162003BDB2F /* DimmedView.swift in Sources */,
0F2A2C6A2239C165003BDB2F /* PanContainerView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
743CABC02226171500634A5A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -586,7 +446,6 @@
DC139075216D9458007A3E64 /* DimmedView.swift in Sources */,
743CABD322265F2E00634A5A /* ProfileViewController.swift in Sources */,
DC139070216D9458007A3E64 /* PanModalPresentationAnimator.swift in Sources */,
944EBA2E227BB7F400C4C97B /* FullScreenNavController.swift in Sources */,
DCA741AE216D90410021F2F2 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -594,77 +453,14 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
0F2A2C572239C119003BDB2F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0F2A2C502239C119003BDB2F /* PanModal */;
targetProxy = 0F2A2C562239C119003BDB2F /* PBXContainerItemProxy */;
};
743CABCA2226171500634A5A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DCA741A9216D90410021F2F2 /* PanModalDemo */;
target = DCA741A9216D90410021F2F2 /* PanModal */;
targetProxy = 743CABC92226171500634A5A /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
0F2A2C5B2239C119003BDB2F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 6UF7FN999R;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = PanModal/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
0F2A2C5C2239C119003BDB2F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 6UF7FN999R;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = PanModal/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
743CABCC2226171500634A5A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -679,9 +475,9 @@
);
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModal.app/PanModal";
};
name = Debug;
};
@ -699,9 +495,9 @@
);
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModal.app/PanModal";
};
name = Release;
};
@ -763,7 +559,6 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@ -818,7 +613,6 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -826,7 +620,6 @@
DCA741BD216D90420021F2F2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_STYLE = Automatic;
@ -839,7 +632,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -847,7 +640,6 @@
DCA741BE216D90420021F2F2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_STYLE = Automatic;
@ -860,7 +652,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@ -868,15 +660,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0F2A2C5A2239C119003BDB2F /* Build configuration list for PBXNativeTarget "PanModal" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0F2A2C5B2239C119003BDB2F /* Debug */,
0F2A2C5C2239C119003BDB2F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -886,7 +669,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModalDemo" */ = {
DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModal" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DCA741BA216D90420021F2F2 /* Debug */,
@ -895,7 +678,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModalDemo" */ = {
DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModal" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DCA741BD216D90420021F2F2 /* Debug */,

View File

@ -5,7 +5,6 @@
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -17,24 +16,19 @@ struct PanModalAnimator {
Constant Animation Properties
*/
struct Constants {
static let defaultTransitionDuration: TimeInterval = 0.5
static let transitionDuration: TimeInterval = 0.5
}
static func animate(_ animations: @escaping PanModalPresentable.AnimationBlockType,
config: PanModalPresentable?,
_ completion: PanModalPresentable.AnimationCompletionType? = nil) {
let transitionDuration = config?.transitionDuration ?? Constants.defaultTransitionDuration
let springDamping = config?.springDamping ?? 1.0
let animationOptions = config?.transitionAnimationOptions ?? []
UIView.animate(withDuration: transitionDuration,
UIView.animate(withDuration: Constants.transitionDuration,
delay: 0,
usingSpringWithDamping: springDamping,
usingSpringWithDamping: config?.springDamping ?? 1.0,
initialSpringVelocity: 0,
options: animationOptions,
options: [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState],
animations: animations,
completion: completion)
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -64,16 +63,11 @@ public class PanModalPresentationAnimator: NSObject {
*/
private func animatePresentation(transitionContext: UIViewControllerContextTransitioning) {
guard
let toVC = transitionContext.viewController(forKey: .to),
let fromVC = transitionContext.viewController(forKey: .from)
guard let toVC = transitionContext.viewController(forKey: .to)
else { return }
let presentable = panModalLayoutType(from: transitionContext)
let presentable = toVC as? PanModalPresentable.LayoutType
// Calls viewWillAppear and viewWillDisappear
fromVC.beginAppearanceTransition(false, animated: true)
// Presents the view in shortForm position, initially
let yPos: CGFloat = presentable?.shortFormYPos ?? 0.0
@ -92,8 +86,6 @@ public class PanModalPresentationAnimator: NSObject {
PanModalAnimator.animate({
panView.frame.origin.y = yPos
}, config: presentable) { [weak self] didComplete in
// Calls viewDidAppear and viewDidDisappear
fromVC.endAppearanceTransition()
transitionContext.completeTransition(didComplete)
self?.feedbackGenerator = nil
}
@ -104,39 +96,20 @@ public class PanModalPresentationAnimator: NSObject {
*/
private func animateDismissal(transitionContext: UIViewControllerContextTransitioning) {
guard
let toVC = transitionContext.viewController(forKey: .to),
let fromVC = transitionContext.viewController(forKey: .from)
guard let fromVC = transitionContext.viewController(forKey: .from)
else { return }
// Calls viewWillAppear and viewWillDisappear
toVC.beginAppearanceTransition(true, animated: true)
let presentable = panModalLayoutType(from: transitionContext)
let presentable = fromVC as? PanModalPresentable.LayoutType
let panView: UIView = transitionContext.containerView.panContainerView ?? fromVC.view
PanModalAnimator.animate({
panView.frame.origin.y = transitionContext.containerView.frame.height
}, config: presentable) { didComplete in
fromVC.view.removeFromSuperview()
// Calls viewDidAppear and viewDidDisappear
toVC.endAppearanceTransition()
transitionContext.completeTransition(didComplete)
}
}
/**
Extracts the PanModal from the transition context, if it exists
*/
private func panModalLayoutType(from context: UIViewControllerContextTransitioning) -> PanModalPresentable.LayoutType? {
switch transitionStyle {
case .presentation:
return context.viewController(forKey: .to) as? PanModalPresentable.LayoutType
case .dismissal:
return context.viewController(forKey: .from) as? PanModalPresentable.LayoutType
}
}
}
// MARK: - UIViewControllerAnimatedTransitioning Delegate
@ -147,17 +120,11 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
Returns the transition duration
*/
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
guard
let context = transitionContext,
let presentable = panModalLayoutType(from: context)
else { return PanModalAnimator.Constants.defaultTransitionDuration }
return presentable.transitionDuration
return PanModalAnimator.Constants.transitionDuration
}
/**
Performs the appropriate animation based on the transition style
Perfroms the appropriate animation based on the transition style
*/
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
switch transitionStyle {
@ -169,4 +136,3 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -23,14 +22,13 @@ import UIKit
By conforming to the PanModalPresentable protocol & overriding values
the presented view can define its layout configuration & presentation.
*/
open class PanModalPresentationController: UIPresentationController {
public class PanModalPresentationController: UIPresentationController {
/**
Enum representing the possible presentation states
*/
public enum PresentationState {
case shortForm
case mediumForm
case longForm
}
@ -38,6 +36,7 @@ open class PanModalPresentationController: UIPresentationController {
Constants
*/
struct Constants {
static let cornerRadius = CGFloat(8.0)
static let indicatorYOffset = CGFloat(8.0)
static let snapMovementSensitivity = CGFloat(0.7)
static let dragIndicatorSize = CGSize(width: 36.0, height: 5.0)
@ -80,17 +79,11 @@ open class PanModalPresentationController: UIPresentationController {
*/
private var shortFormYPosition: CGFloat = 0
private var mediumFormYPosition: CGFloat = 0
/**
The y value for the long form presentation state
*/
private var longFormYPosition: CGFloat = 0
private var allowsDragToDismiss: Bool {
presentable?.onDragToDismiss != nil
}
/**
Determine anchored Y postion based on the `anchorModalToLongForm` flag
*/
@ -112,14 +105,14 @@ open class PanModalPresentationController: UIPresentationController {
Background view used as an overlay over the presenting view
*/
private lazy var backgroundView: DimmedView = {
let view: DimmedView = presentable?.dimmedView ?? DimmedView()
if let color = presentable?.panModalBackgroundColor {
view.backgroundColor = color
let view: DimmedView
if let alpha = presentable?.backgroundAlpha {
view = DimmedView(dimAlpha: alpha)
} else {
view = DimmedView()
}
view.didTap = { [weak self] _ in
if self?.presentable?.allowsTapToDismiss == true {
self?.presentable?.onTapToDismiss?()
}
self?.dismissPresentedViewController()
}
return view
}()
@ -139,7 +132,7 @@ open class PanModalPresentationController: UIPresentationController {
*/
private lazy var dragIndicatorView: UIView = {
let view = UIView()
view.backgroundColor = presentable?.dragIndicatorBackgroundColor
view.backgroundColor = .lightGray
view.layer.cornerRadius = Constants.dragIndicatorSize.height / 2.0
return view
}()
@ -192,10 +185,24 @@ open class PanModalPresentationController: UIPresentationController {
}
coordinator.animate(alongsideTransition: { [weak self] _ in
if let yPos = self?.shortFormYPosition {
self?.adjust(toYPosition: yPos)
}
self?.presentedViewController.setNeedsStatusBarAppearanceUpdate()
self?.backgroundView.dimState = .max
})
}
override public func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
backgroundView.dimState = .off
return
}
/**
Drag indicator is drawn outside of view bounds
so hiding it on view dismiss means avoids visual bugs
*/
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.dragIndicatorView.alpha = 0.0
self?.backgroundView.dimState = .off
})
}
@ -205,50 +212,6 @@ open class PanModalPresentationController: UIPresentationController {
backgroundView.removeFromSuperview()
}
override public func dismissalTransitionWillBegin() {
presentable?.panModalWillDismiss()
guard let coordinator = presentedViewController.transitionCoordinator else {
backgroundView.dimState = .off
return
}
/**
Drag indicator is drawn outside of view bounds
so hiding it on view dismiss means avoiding visual bugs
*/
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.dragIndicatorView.alpha = 0.0
self?.backgroundView.dimState = .off
self?.presentingViewController.setNeedsStatusBarAppearanceUpdate()
})
}
override public func dismissalTransitionDidEnd(_ completed: Bool) {
if !completed { return }
presentable?.panModalDidDismiss()
}
/**
Update presented view size in response to size class changes
*/
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [weak self] _ in
guard
let self = self,
let presentable = self.presentable
else { return }
self.adjustPresentedViewFrame()
if presentable.shouldRoundTopCorners {
self.addRoundedCorners(to: self.presentedView)
}
})
}
}
// MARK: - Public Methods
@ -256,10 +219,10 @@ open class PanModalPresentationController: UIPresentationController {
public extension PanModalPresentationController {
/**
Transition the PanModalPresentationController
Tranisition the PanModalPresentationController
to the given presentation state
*/
func transition(to state: PresentationState) {
public func transition(to state: PresentationState) {
guard presentable?.shouldTransition(to: state) == true
else { return }
@ -269,48 +232,52 @@ public extension PanModalPresentationController {
switch state {
case .shortForm:
snap(toYPosition: shortFormYPosition)
case .mediumForm:
snap(toYPosition: mediumFormYPosition)
case .longForm:
snap(toYPosition: longFormYPosition)
}
}
/**
Operations on the scroll view, such as content height changes,
or when inserting/deleting rows can cause the pan modal to jump,
caused by the pan modal responding to content offset changes.
Set the content offset of the scroll view
To avoid this, you can call this method to perform scroll view updates,
with scroll observation temporarily disabled.
Due to content offset observation, its not possible to programmatically
set the content offset directly on the scroll view while in the short form.
This method pauses the content offset KVO, performs the content offset chnage
and then resumes content offset observation.
*/
func performUpdates(_ updates: () -> Void) {
public func setContentOffset(offset: CGPoint) {
guard let scrollView = presentable?.panScrollable
else { return }
// Pause scroll observer
/**
Invalidate scroll view observer
to prevent its overriding the content offset change
*/
scrollObserver?.invalidate()
scrollObserver = nil
// Perform updates
updates()
// Resume scroll observer
/**
Set scroll view offset & track scrolling
*/
scrollView.setContentOffset(offset, animated:false)
trackScrolling(scrollView)
/**
Add the scroll view observer
*/
observe(scrollView: scrollView)
}
/**
Updates the PanModalPresentationController layout
based on values in the PanModalPresentable
based on values in the PanModalPresentabls
- Note: This should be called whenever any
pan modal presentable value changes after the initial presentation
*/
func setNeedsLayoutUpdate() {
public func setNeedsLayoutUpdate() {
configureViewLayout()
adjustPresentedViewFrame()
observe(scrollView: presentable?.panScrollable)
@ -329,7 +296,7 @@ private extension PanModalPresentationController {
var isPresentedViewAnchored: Bool {
if !isPresentedViewAnimating
&& extendsPanScrolling
&& presentedView.frame.minY.rounded() <= anchoredYPosition.rounded() {
&& presentedView.frame.minY <= anchoredYPosition {
return true
}
@ -374,24 +341,9 @@ private extension PanModalPresentationController {
Reduce height of presentedView so that it sits at the bottom of the screen
*/
func adjustPresentedViewFrame() {
guard let frame = containerView?.frame
else { return }
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
let panFrame = panContainerView.frame
panContainerView.frame.size = frame.size
let positions = [shortFormYPosition, mediumFormYPosition, longFormYPosition]
if !positions.contains(panFrame.origin.y) {
// if the container is already in the correct position, no need to adjust positioning
// (rotations & size changes cause positioning to be out of sync)
let yPosition = panFrame.origin.y - panFrame.height + frame.height
presentedView.frame.origin.y = max(yPosition, anchoredYPosition)
}
panContainerView.frame.origin.x = frame.origin.x
presentedViewController.view.frame = CGRect(origin: .zero, size: adjustedSize)
let frame = containerView?.frame ?? .zero
let size = CGSize(width: frame.size.width, height: frame.height - anchoredYPosition)
presentedViewController.view.frame = CGRect(origin: .zero, size: size)
}
/**
@ -431,7 +383,7 @@ private extension PanModalPresentationController {
}
/**
Calculates & stores the layout anchor points & options
Caluclates & stores the layout anchor points & options
*/
func configureViewLayout() {
@ -439,7 +391,6 @@ private extension PanModalPresentationController {
else { return }
shortFormYPosition = layoutPresentable.shortFormYPos
mediumFormYPosition = layoutPresentable.mediumFormYPos
longFormYPosition = layoutPresentable.longFormYPos
anchorModalToLongForm = layoutPresentable.anchorModalToLongForm
extendsPanScrolling = layoutPresentable.allowsExtendedPanScrolling
@ -462,21 +413,14 @@ private extension PanModalPresentationController {
to avoid visual bugs
*/
scrollView.showsVerticalScrollIndicator = false
scrollView.isScrollEnabled = presentable?.isPanScrollEnabled ?? true
scrollView.scrollIndicatorInsets = presentable?.scrollIndicatorInsets ?? .zero
/**
Set the appropriate contentInset as the configuration within this class
offsets it
*/
let bottomInset: CGFloat
if #available(iOS 11.0, *) {
bottomInset = presentingViewController.view.safeAreaInsets.bottom
} else {
bottomInset = presentingViewController.bottomLayoutGuide.length
}
scrollView.contentInset.bottom = bottomInset
scrollView.contentInset.bottom = presentingViewController.bottomLayoutGuide.length
/**
As we adjust the bounds during `handleScrollViewTopBounce`
@ -499,7 +443,8 @@ private extension PanModalPresentationController {
@objc func didPanOnPresentedView(_ recognizer: UIPanGestureRecognizer) {
guard
shouldRespond(to: recognizer),
presentable?.isPanScrollEnabled == true,
!shouldFail(panGestureRecognizer: recognizer),
let containerView = containerView
else {
recognizer.setTranslation(.zero, in: recognizer.view)
@ -540,18 +485,12 @@ private extension PanModalPresentationController {
if velocity.y < 0 {
transition(to: .longForm)
} else if nearest(to: presentedView.frame.minY,
inValues: [mediumFormYPosition, containerView.bounds.height]) == mediumFormYPosition
&& presentedView.frame.minY < mediumFormYPosition {
transition(to: .mediumForm)
} else if (nearest(to: presentedView.frame.minY,
inValues: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
&& presentedView.frame.minY < shortFormYPosition) || allowsDragToDismiss == false {
} else if (nearestDistance(to: presentedView.frame.minY, inDistances: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
&& presentedView.frame.minY < shortFormYPosition) || presentable?.allowsDragToDismiss == false {
transition(to: .shortForm)
} else {
presentable?.onDragToDismiss?()
dismissPresentedViewController()
}
} else {
@ -560,48 +499,21 @@ private extension PanModalPresentationController {
The `containerView.bounds.height` is used to determine
how close the presented view is to the bottom of the screen
*/
let position = nearest(to: presentedView.frame.minY,
inValues: [containerView.bounds.height,
shortFormYPosition,
mediumFormYPosition,
longFormYPosition])
let position = nearestDistance(to: presentedView.frame.minY, inDistances: [containerView.bounds.height, shortFormYPosition, longFormYPosition])
if position == longFormYPosition {
transition(to: .longForm)
} else if position == mediumFormYPosition {
transition(to: .mediumForm)
} else if position == shortFormYPosition || allowsDragToDismiss == false {
} else if position == shortFormYPosition || presentable?.allowsDragToDismiss == false {
transition(to: .shortForm)
} else {
presentable?.onDragToDismiss?()
dismissPresentedViewController()
}
}
}
}
/**
Determine if the pan modal should respond to the gesture recognizer.
If the pan modal is already being dragged & the delegate returns false, ignore until
the recognizer is back to it's original state (.began)
This is the only time we should be cancelling the pan modal gesture recognizer
*/
func shouldRespond(to panGestureRecognizer: UIPanGestureRecognizer) -> Bool {
guard
presentable?.shouldRespond(to: panGestureRecognizer) == true ||
!(panGestureRecognizer.state == .began || panGestureRecognizer.state == .cancelled)
else {
panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true
return false
}
return !shouldFail(panGestureRecognizer: panGestureRecognizer)
}
/**
Communicate intentions to presentable and adjust subviews in containerView
*/
@ -637,7 +549,7 @@ private extension PanModalPresentationController {
Allow api consumers to override the internal conditions &
decide if the pan gesture recognizer should be prioritized.
This is the only time we should be cancelling the panScrollable recognizer,
This is the only time we should be cancelling a recognizer,
for the purpose of ensuring we're no longer tracking the scrollView
*/
guard !shouldPrioritize(panGestureRecognizer: panGestureRecognizer) else {
@ -664,7 +576,7 @@ private extension PanModalPresentationController {
*/
func shouldPrioritize(panGestureRecognizer: UIPanGestureRecognizer) -> Bool {
return panGestureRecognizer.state == .began &&
presentable?.shouldPrioritize(panModalGestureRecognizer: panGestureRecognizer) == true
presentable?.shouldPrioritize(panModalGestureRecognizer: panGestureRecognizer) ?? false
}
/**
@ -688,23 +600,40 @@ private extension PanModalPresentationController {
*/
func adjust(toYPosition yPos: CGFloat) {
presentedView.frame.origin.y = max(yPos, anchoredYPosition)
guard presentedView.frame.origin.y > shortFormYPosition else {
backgroundView.dimState = .max
return
}
let maxHeight = UIScreen.main.bounds.height - longFormYPosition
let yDisplacementFromShortForm = presentedView.frame.origin.y - shortFormYPosition
backgroundView.dimState = .percent(1.0 - presentedView.frame.origin.y / maxHeight)
/**
Once presentedView is translated below shortForm, calculate yPos relative to bottom of screen
and apply percentage to backgroundView alpha
*/
backgroundView.dimState = .percent(1.0 - (yDisplacementFromShortForm / presentedView.frame.height))
}
/**
Finds the nearest value to a given number out of a given array of float values
Finds the nearest distance to a given position out of a given array of distance values
- Parameters:
- number: reference float we are trying to find the closest value to
- values: array of floats we would like to compare against
- position: reference postion we are trying to find the closest distance to
- distances: array of positions we would like to compare against
*/
func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat {
guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) })
else { return number }
return nearestVal
func nearestDistance(to position: CGFloat, inDistances distances: [CGFloat]) -> CGFloat {
guard let nearestDistance = distances.min(by: { abs(position - $0) < abs(position - $1) })
else { return position }
return nearestDistance
}
/**
Dismiss presented view
*/
func dismissPresentedViewController() {
presentable?.panModalWillDismiss()
presentedViewController.dismiss(animated: true, completion: nil)
}
}
@ -810,7 +739,7 @@ private extension PanModalPresentationController {
*/
func handleScrollViewTopBounce(scrollView: UIScrollView, change: NSKeyValueObservedChange<CGPoint>) {
guard let oldYValue = change.oldValue?.y, scrollView.isDecelerating
guard let oldYValue = change.oldValue?.y
else { return }
let yOffset = scrollView.contentOffset.y
@ -850,11 +779,11 @@ extension PanModalPresentationController: UIGestureRecognizerDelegate {
}
/**
Allow simultaneous gesture recognizers only when the other gesture recognizer's view
is the pan scrollable view
Allow simultaneous gesture recognizers only when the other gesture recognizer
is a pan gesture recognizer
*/
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view == presentable?.panScrollable
return otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
}
}
@ -868,23 +797,37 @@ private extension PanModalPresentationController {
because we render the dragIndicator outside of view bounds
*/
func addRoundedCorners(to view: UIView) {
let radius = presentable?.cornerRadius ?? 0
let path = UIBezierPath(roundedRect: view.bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: radius, height: radius))
// Draw around the drag indicator view, if displayed
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: Constants.cornerRadius))
// 1. Draw left rounded corner
path.addArc(withCenter: CGPoint(x: path.currentPoint.x + Constants.cornerRadius, y: path.currentPoint.y),
radius: Constants.cornerRadius, startAngle: .pi, endAngle: 3.0 * .pi/2.0, clockwise: true)
// 2. Draw around the drag indicator view, if displayed
if presentable?.showDragIndicator == true {
let indicatorLeftEdgeXPos = view.bounds.width/2.0 - Constants.dragIndicatorSize.width/2.0
drawAroundDragIndicator(currentPath: path, indicatorLeftEdgeXPos: indicatorLeftEdgeXPos)
}
// Set path as a mask to display optional drag indicator view & rounded corners
// 3. Draw line to right side of presented view, leaving space to draw rounded corner
path.addLine(to: CGPoint(x: view.bounds.width - Constants.cornerRadius, y: path.currentPoint.y))
// 4. Draw right rounded corner
path.addArc(withCenter: CGPoint(x: path.currentPoint.x, y: path.currentPoint.y + Constants.cornerRadius),
radius: Constants.cornerRadius, startAngle: 3.0 * .pi/2.0, endAngle: 0, clockwise: true)
// 5. Draw around final edges of view
path.addLine(to: CGPoint(x: path.currentPoint.x, y: view.bounds.height))
path.addLine(to: CGPoint(x: 0, y: path.currentPoint.y))
// 6. Set path as a mask to display optional drag indicator view & rounded corners
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
// Improve performance by rasterizing the layer
// 7. Improve performance by rasterizing the layer
view.layer.shouldRasterize = true
view.layer.rasterizationScale = UIScreen.main.scale
}
@ -915,4 +858,3 @@ private extension UIScrollView {
return isDragging && !isDecelerating || isTracking
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -74,8 +73,8 @@ extension PanModalPresentationDelegate: UIAdaptivePresentationControllerDelegate
Dismisses the presented view controller
*/
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
controller.presentedViewController.dismiss(animated: false, completion: nil)
return .none
}
}
#endif

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -1,19 +0,0 @@
//
// PanModal.h
// PanModal
//
// Created by Tosin A on 3/13/19.
// Copyright © 2019 Detail. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for PanModal.
FOUNDATION_EXPORT double PanModalVersionNumber;
//! Project version string for PanModal.
FOUNDATION_EXPORT const unsigned char PanModalVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <PanModal/PublicHeader.h>

View File

@ -1,66 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct ModalViewPresentationDetent: Hashable {
// MARK: - Default Values
public static var headerOnly: ModalViewPresentationDetent {
ModalViewPresentationDetent(height: CGFloat(Int.min))
}
public static func height(_ height: CGFloat) -> ModalViewPresentationDetent {
ModalViewPresentationDetent(height: height)
}
public static var maxHeight: ModalViewPresentationDetent {
ModalViewPresentationDetent(height: CGFloat(Int.max))
}
// MARK: - Public Properties
public var height: CGFloat
// MARK: - Internal Methods
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
if self == .headerOnly {
return .contentHeight(headerHeight)
}
if self == .maxHeight {
return .maxHeight
}
return .contentHeight(height)
}
}
// MARK: - Comparable
extension ModalViewPresentationDetent: Comparable {
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
lhs.height < rhs.height
}
}

View File

@ -1,11 +1,10 @@
//
// PanModalHeight.swift
// PanModal
// SlackUI
//
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -36,9 +35,4 @@ public enum PanModalHeight: Equatable {
*/
case contentHeightIgnoringSafeArea(CGFloat)
/**
Sets the height to be the intrinsic content height
*/
case intrinsicHeight
}
#endif

View File

@ -2,10 +2,10 @@
// PanModalPresentable+Defaults.swift
// PanModal
//
// Created by Stephen Sowole on 11/5/18.
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -13,30 +13,14 @@ import UIKit
*/
public extension PanModalPresentable where Self: UIViewController {
var onTapToDismiss: (() -> Void)? {
{ [weak self] in
self?.dismiss(animated: true)
}
}
var onDragToDismiss: (() -> Void)? {
{ [weak self] in
self?.dismiss(animated: true)
}
}
var topOffset: CGFloat {
topLayoutOffset
return topLayoutOffset + 21.0
}
var shortFormHeight: PanModalHeight {
return longFormHeight
}
var mediumFormHeight: PanModalHeight {
longFormHeight
}
var longFormHeight: PanModalHeight {
guard let scrollView = panScrollable
@ -47,36 +31,16 @@ public extension PanModalPresentable where Self: UIViewController {
return .contentHeight(scrollView.contentSize.height)
}
var dimmedView: DimmedView? {
DimmedView()
}
var cornerRadius: CGFloat {
return 8.0
}
var springDamping: CGFloat {
return 0.8
}
var transitionDuration: Double {
return PanModalAnimator.Constants.defaultTransitionDuration
}
var transitionAnimationOptions: UIView.AnimationOptions {
return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState]
}
var panModalBackgroundColor: UIColor {
return UIColor.black.withAlphaComponent(0.7)
}
var dragIndicatorBackgroundColor: UIColor {
return UIColor.lightGray
var backgroundAlpha: CGFloat {
return 0.7
}
var scrollIndicatorInsets: UIEdgeInsets {
let top = shouldRoundTopCorners ? cornerRadius : 0
let top = shouldRoundTopCorners ? PanModalPresentationController.Constants.cornerRadius : 0
return UIEdgeInsets(top: CGFloat(top), left: 0, bottom: bottomLayoutOffset, right: 0)
}
@ -97,7 +61,7 @@ public extension PanModalPresentable where Self: UIViewController {
return true
}
var allowsTapToDismiss: Bool {
var isPanScrollEnabled: Bool {
return true
}
@ -117,11 +81,7 @@ public extension PanModalPresentable where Self: UIViewController {
return shouldRoundTopCorners
}
func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool {
return true
}
func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) {
func willRespond(to panGestureRecognizer: UIPanGestureRecognizer) {
}
@ -141,8 +101,4 @@ public extension PanModalPresentable where Self: UIViewController {
}
func panModalDidDismiss() {
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -27,11 +26,7 @@ extension PanModalPresentable where Self: UIViewController {
Gives us the safe area inset from the top.
*/
var topLayoutOffset: CGFloat {
guard let rootVC = rootViewController
else { return 0}
if #available(iOS 11.0, *) { return rootVC.view.safeAreaInsets.top } else { return rootVC.topLayoutGuide.length }
return UIApplication.shared.keyWindow?.rootViewController?.topLayoutGuide.length ?? 0
}
/**
@ -39,15 +34,11 @@ extension PanModalPresentable where Self: UIViewController {
Gives us the safe area inset from the bottom.
*/
var bottomLayoutOffset: CGFloat {
guard let rootVC = rootViewController
else { return 0}
if #available(iOS 11.0, *) { return rootVC.view.safeAreaInsets.bottom } else { return rootVC.bottomLayoutGuide.length }
return UIApplication.shared.keyWindow?.rootViewController?.bottomLayoutGuide.length ?? 0
}
/**
Returns the short form Y position
Returns the short form Y postion
- Note: If voiceover is on, the `longFormYPos` is returned.
We do not support short form when voiceover is on as it would make it difficult for user to navigate.
@ -63,14 +54,8 @@ extension PanModalPresentable where Self: UIViewController {
return max(shortFormYPos, longFormYPos)
}
var mediumFormYPos: CGFloat {
let mediumFormYPos = topMargin(from: mediumFormHeight)
return max(mediumFormYPos, longFormYPos)
}
/**
Returns the long form Y position
Returns the long form Y postion
- Note: We cap this value to the max possible height
to ensure content is not rendered outside of the view bounds
@ -105,22 +90,7 @@ extension PanModalPresentable where Self: UIViewController {
return bottomYPos - (height + bottomLayoutOffset)
case .contentHeightIgnoringSafeArea(let height):
return bottomYPos - height
case .intrinsicHeight:
view.layoutIfNeeded()
let targetSize = CGSize(width: (presentedVC?.containerView?.bounds ?? UIScreen.main.bounds).width,
height: UIView.layoutFittingCompressedSize.height)
let intrinsicHeight = view.systemLayoutSizeFitting(targetSize).height
return bottomYPos - (intrinsicHeight + bottomLayoutOffset)
}
}
private var rootViewController: UIViewController? {
guard let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication
else { return nil }
return application.keyWindow?.rootViewController
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -30,6 +29,16 @@ public extension PanModalPresentable where Self: UIViewController {
presentedVC?.transition(to: state)
}
/**
Programmatically set the content offset of the pan scrollable.
This is required to use while in the short form presentation state,
as due to content offset observation, setting the content offset directly would fail
*/
func panModalSetContentOffset(offset: CGPoint) {
presentedVC?.setContentOffset(offset: offset)
}
/**
A function wrapper over the `setNeedsLayoutUpdate()`
function in the PanModalPresentationController.
@ -40,16 +49,6 @@ public extension PanModalPresentable where Self: UIViewController {
presentedVC?.setNeedsLayoutUpdate()
}
/**
Operations on the scroll view, such as content height changes, or when inserting/deleting rows can cause the pan modal to jump,
caused by the pan modal responding to content offset changes.
To avoid this, you can call this method to perform scroll view updates, with scroll observation temporarily disabled.
*/
func panModalPerformUpdates(_ updates: () -> Void) {
presentedVC?.performUpdates(updates)
}
/**
A function wrapper over the animate function in PanModalAnimator.
@ -60,4 +59,3 @@ public extension PanModalPresentable where Self: UIViewController {
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -14,12 +13,12 @@ import UIKit
Usage:
```
extension YourViewController: PanModalPresentable {
extension UIViewController: PanModalPresentable {
func shouldRoundTopCorners: Bool { return false }
}
```
*/
public protocol PanModalPresentable: AnyObject {
public protocol PanModalPresentable {
/**
The scroll view embedded in the view controller.
@ -45,8 +44,6 @@ public protocol PanModalPresentable: AnyObject {
*/
var shortFormHeight: PanModalHeight { get }
var mediumFormHeight: PanModalHeight { get }
/**
The height of the pan modal container view
when in the longForm presentation state.
@ -57,14 +54,6 @@ public protocol PanModalPresentable: AnyObject {
*/
var longFormHeight: PanModalHeight { get }
var dimmedView: DimmedView? { get }
/**
The corner radius used when `shouldRoundTopCorners` is enabled.
Default Value is 8.0.
*/
var cornerRadius: CGFloat { get }
/**
The springDamping value used to determine the amount of 'bounce'
seen when transitioning to short/long form.
@ -74,36 +63,13 @@ public protocol PanModalPresentable: AnyObject {
var springDamping: CGFloat { get }
/**
The transitionDuration value is used to set the speed of animation during a transition,
including initial presentation.
Default value is 0.5.
*/
var transitionDuration: Double { get }
/**
The animation options used when performing animations on the PanModal, utilized mostly
during a transition.
Default value is [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState].
*/
var transitionAnimationOptions: UIView.AnimationOptions { get }
/**
The background view color.
The background view alpha.
- Note: This is only utilized at the very start of the transition.
Default Value is black with alpha component 0.7.
*/
var panModalBackgroundColor: UIColor { get }
/**
The drag indicator view color.
Default value is light gray.
*/
var dragIndicatorBackgroundColor: UIColor { get }
Default Value is 0.7.
*/
var backgroundAlpha: CGFloat { get }
/**
We configure the panScrollable's scrollIndicatorInsets interally so override this value
@ -125,29 +91,28 @@ public protocol PanModalPresentable: AnyObject {
A flag to determine if scrolling should seamlessly transition from the pan modal container view to
the embedded scroll view once the scroll limit has been reached.
Default value is false. Unless a scrollView is provided and the content height exceeds the longForm height.
Default value is false.
Unless a scrollView is provided and the content exceeds the longForm height
*/
var allowsExtendedPanScrolling: Bool { get }
/**
A flag to determine if dismissal should be initiated when swiping down on the presented view.
Return false to fallback to the short form state instead of dismissing.
Default value is true.
*/
var allowsDragToDismiss: Bool { get }
var onTapToDismiss: (() -> Void)? { get }
var onDragToDismiss: (() -> Void)? { get }
/**
A flag to determine if dismissal should be initiated when tapping on the dimmed background view.
A flag to determine if scrolling should be enabled on the entire view.
- Note: Returning false will disable scrolling on the embedded scrollview as well as on the
pan modal container view.
Default value is true.
*/
var allowsTapToDismiss: Bool { get }
var isPanScrollEnabled: Bool { get }
/**
A flag to toggle user interactions on the container view.
@ -180,15 +145,6 @@ public protocol PanModalPresentable: AnyObject {
*/
var showDragIndicator: Bool { get }
/**
Asks the delegate if the pan modal should respond to the pan modal gesture recognizer.
Return false to disable movement on the pan modal but maintain gestures on the presented view.
Default value is true.
*/
func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool
/**
Notifies the delegate when the pan modal gesture recognizer state is either
`began` or `changed`. This method gives the delegate a chance to prepare
@ -198,7 +154,7 @@ public protocol PanModalPresentable: AnyObject {
Default value is an empty implementation.
*/
func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer)
func willRespond(to panGestureRecognizer: UIPanGestureRecognizer)
/**
Asks the delegate if the pan modal gesture recognizer should be prioritized.
@ -206,8 +162,8 @@ public protocol PanModalPresentable: AnyObject {
For example, you can use this to define a region
where you would like to restrict where the pan gesture can start.
If false, then we rely solely on the internal conditions of when a pan gesture
should succeed or fail, such as, if we're actively scrolling on the scrollView.
If false, then we rely on the internal conditions of when a pan gesture
should succeed or fail, such as, if we're actively scrolling on the scrollView
Default return value is false.
*/
@ -234,11 +190,4 @@ public protocol PanModalPresentable: AnyObject {
*/
func panModalWillDismiss()
/**
Notifies the delegate after the pan modal is dismissed.
Default value is an empty implementation.
*/
func panModalDidDismiss()
}
#endif

View File

@ -1,11 +1,10 @@
//
// PanModalPresenter.swift
// PanModal
// SlackUI
//
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -18,7 +17,7 @@ import UIKit
sourceRect: .zero)
```
*/
protocol PanModalPresenter: AnyObject {
public protocol PanModalPresenter {
/**
A flag that returns true if the current presented view controller
@ -29,10 +28,6 @@ protocol PanModalPresenter: AnyObject {
/**
Presents a view controller that conforms to the PanModalPresentable protocol
*/
func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType,
sourceView: UIView?,
sourceRect: CGRect,
completion: (() -> Void)?)
func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType, sourceView: UIView?, sourceRect: CGRect)
}
#endif

View File

@ -1,11 +1,10 @@
//
// UIViewController+PanModalPresenterProtocol.swift
// PanModal
// SlackUI
//
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -35,14 +34,10 @@ extension UIViewController: PanModalPresenter {
- viewControllerToPresent: The view controller to be presented
- sourceView: The view containing the anchor rectangle for the popover.
- sourceRect: The rectangle in the specified view in which to anchor the popover.
- completion: The block to execute after the presentation finishes. You may specify nil for this parameter.
- Note: sourceView & sourceRect are only required for presentation on an iPad.
*/
public func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType,
sourceView: UIView? = nil,
sourceRect: CGRect = .zero,
completion: (() -> Void)? = nil) {
public func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType, sourceView: UIView? = nil, sourceRect: CGRect = .zero) {
/**
Here, we deliberately do not check for size classes. More info in `PanModalPresentationDelegate`
@ -55,12 +50,10 @@ extension UIViewController: PanModalPresenter {
viewControllerToPresent.popoverPresentationController?.delegate = PanModalPresentationDelegate.default
} else {
viewControllerToPresent.modalPresentationStyle = .custom
viewControllerToPresent.modalPresentationCapturesStatusBarAppearance = true
viewControllerToPresent.transitioningDelegate = PanModalPresentationDelegate.default
}
present(viewControllerToPresent, animated: true, completion: completion)
present(viewControllerToPresent, animated: true, completion: nil)
}
}
#endif

View File

@ -5,19 +5,18 @@
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
A dim view for use as an overlay over content you want dimmed.
*/
open class DimmedView: UIView {
public class DimmedView: UIView {
/**
Represents the possible states of the dimmed view.
max, off or a percentage of dimAlpha.
*/
public enum DimState {
enum DimState {
case max
case off
case percent(CGFloat)
@ -30,14 +29,22 @@ open class DimmedView: UIView {
*/
var dimState: DimState = .off {
didSet {
onChange(dimState: dimState)
switch dimState {
case .max:
alpha = dimAlpha
case .off:
alpha = 0.0
case .percent(let percentage):
let val = max(0.0, min(1.0, percentage))
alpha = dimAlpha * val
}
}
}
/**
The closure to be executed when a tap occurs
*/
var didTap: ((_ recognizer: UITapGestureRecognizer) -> Void)?
var didTap: ((_ recognizer: UIGestureRecognizer) -> Void)?
/**
Tap gesture recognizer
@ -46,11 +53,15 @@ open class DimmedView: UIView {
return UITapGestureRecognizer(target: self, action: #selector(didTapView))
}()
private let dimAlpha: CGFloat
// MARK: - Initializers
public init() {
init(dimAlpha: CGFloat = 0.7) {
self.dimAlpha = dimAlpha
super.init(frame: .zero)
alpha = 0.0
backgroundColor = .black
addGestureRecognizer(tapGesture)
}
@ -60,22 +71,8 @@ open class DimmedView: UIView {
// MARK: - Event Handlers
@objc private func didTapView(sender: UITapGestureRecognizer) {
didTap?(sender)
}
// MARK: - Subclass override
open func onChange(dimState: DimState) {
switch dimState {
case .max:
alpha = 1.0
case .off:
alpha = 0.0
case .percent(let percentage):
alpha = max(0.0, min(1.0, percentage))
}
@objc private func didTapView() {
didTap?(tapGesture)
}
}
#endif

View File

@ -5,7 +5,6 @@
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
//
#if os(iOS)
import UIKit
/**
@ -21,9 +20,8 @@ class PanContainerView: UIView {
addSubview(presentedView)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
fatalError()
}
}
@ -35,10 +33,7 @@ extension UIView {
from the view hierachy
*/
var panContainerView: PanContainerView? {
return subviews.first(where: { view -> Bool in
view is PanContainerView
}) as? PanContainerView
return subviews.compactMap({ $0 as? PanContainerView }).first
}
}
#endif

View File

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
BuildableName = "PanModal.framework"
BlueprintName = "PanModal"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
BuildableName = "PanModal.framework"
BlueprintName = "PanModal"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
BuildableName = "PanModal.framework"
BlueprintName = "PanModal"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
BuildableName = "PanModalDemo.app"
BlueprintName = "PanModalDemo"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "743CABC32226171500634A5A"
BuildableName = "PanModalTests.xctest"
BlueprintName = "PanModalTests"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
BuildableName = "PanModalDemo.app"
BlueprintName = "PanModalDemo"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
BuildableName = "PanModalDemo.app"
BlueprintName = "PanModalDemo"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
BuildableName = "PanModalDemo.app"
BlueprintName = "PanModalDemo"
ReferencedContainer = "container:PanModalDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,13 +1,10 @@
### PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
<p align="center">
<img src="https://github.com/slackhq/PanModal/raw/master/Screenshots/panModal.gif" width="30%" height="30%" alt="Screenshot Preview" />
</p>
<p align="center">
<img src="https://img.shields.io/badge/Platform-iOS_10+-green.svg" alt="Platform: iOS 10.0+" />
<a href="https://developer.apple.com/swift" target="_blank"><img src="https://img.shields.io/badge/Language-Swift_5-blueviolet.svg" alt="Language: Swift 5" /></a>
<a href="https://developer.apple.com/swift" target="_blank"><img src="https://img.shields.io/badge/Language-Swift_4-blueviolet.svg" alt="Language: Swift 4" /></a>
<a href="https://cocoapods.org/pods/PanModal" target="_blank"><img src="https://img.shields.io/badge/CocoaPods-v1.0-red.svg" alt="CocoaPods compatible" /></a>
<a href="https://github.com/Carthage/Carthage" target="_blank"><img src="https://img.shields.io/badge/Carthage-compatible-blue.svg" alt="Carthage compatible" /></a>
<img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT" />
@ -24,12 +21,6 @@
<a href="#license">License</a>
</p>
<p align="center">
Read our <a href="https://slack.engineering/panmodal-better-support-for-thumb-accessibility-on-slack-mobile-52b2a7596031" target="_blank">blog</a> on how Slack is getting more :thumbsup: with PanModal
Swift 4.2 support can be found on the `Swift4.2` branch.
</p>
## Features
* Supports any type of `UIViewController`
@ -54,14 +45,6 @@ pod 'PanModal'
github "slackhq/PanModal"
```
* <a href="https://swift.org/package-manager/" target="_blank">Swift Package Manager</a>:
```swift
dependencies: [
.package(url: "https://github.com/slackhq/PanModal.git", .exact("1.2.6")),
],
```
## Usage
PanModal was designed to be used effortlessly. Simply call `presentPanModal` in the same way you would expect to present a `UIViewController`
@ -147,7 +130,7 @@ We will only be fixing critical bugs, thus, for any non-critical issues or featu
## Authors
[Stephen Sowole](https://github.com/ste57) • [Tosin Afolabi](https://github.com/tosinaf)
[Stephen Sowole](https://github.com/tun57) • [Tosin Afolabi](https://github.com/tosinaf)
## License

View File

@ -67,7 +67,6 @@ private extension SampleViewController {
enum RowType: Int, CaseIterable {
case basic
case fullScreen
case alert
case transientAlert
case userGroups
@ -78,7 +77,6 @@ private extension SampleViewController {
var presentable: RowPresentable {
switch self {
case .basic: return Basic()
case .fullScreen: return FullScreen()
case .alert: return Alert()
case .transientAlert: return TransientAlert()
case .userGroups: return UserGroup()
@ -88,38 +86,33 @@ private extension SampleViewController {
}
struct Basic: RowPresentable {
let string: String = "Basic"
let rowVC: PanModalPresentable.LayoutType = BasicViewController()
}
struct FullScreen: RowPresentable {
let string: String = "Full Screen"
let rowVC: PanModalPresentable.LayoutType = FullScreenNavController()
var string: String { return "Basic" }
var rowVC: PanModalPresentable.LayoutType { return BasicViewController() }
}
struct Alert: RowPresentable {
let string: String = "Alert"
let rowVC: PanModalPresentable.LayoutType = AlertViewController()
var string: String { return "Alert" }
var rowVC: PanModalPresentable.LayoutType { return AlertViewController() }
}
struct TransientAlert: RowPresentable {
let string: String = "Alert (Transient)"
let rowVC: PanModalPresentable.LayoutType = TransientAlertViewController()
var string: String { return "Alert (Transient)"}
var rowVC: PanModalPresentable.LayoutType { return TransientAlertViewController() }
}
struct UserGroup: RowPresentable {
let string: String = "User Groups"
let rowVC: PanModalPresentable.LayoutType = UserGroupViewController()
var string: String { return "User Groups" }
var rowVC: PanModalPresentable.LayoutType { return UserGroupViewController() }
}
struct Navigation: RowPresentable {
let string: String = "User Groups (NavigationController)"
let rowVC: PanModalPresentable.LayoutType = NavigationController()
var string: String { return "User Groups (NavigationController)" }
var rowVC: PanModalPresentable.LayoutType { return NavigationController() }
}
struct Stacked: RowPresentable {
let string: String = "User Groups (Stacked)"
let rowVC: PanModalPresentable.LayoutType = UserGroupStackedViewController()
var string: String { return "User Groups (Stacked)" }
var rowVC: PanModalPresentable.LayoutType { return UserGroupStackedViewController() }
}
}
}

View File

@ -59,8 +59,8 @@ class TransientAlertViewController: AlertViewController {
return true
}
override var panModalBackgroundColor: UIColor {
return .clear
override var backgroundAlpha: CGFloat {
return 0.0
}
override var isUserInteractionEnabled: Bool {

View File

@ -46,8 +46,8 @@ class AlertViewController: UIViewController, PanModalPresentable {
return shortFormHeight
}
var panModalBackgroundColor: UIColor {
return UIColor.black.withAlphaComponent(0.1)
var backgroundAlpha: CGFloat {
return 0.1
}
var shouldRoundTopCorners: Bool {

View File

@ -18,10 +18,6 @@ class BasicViewController: UIViewController {
extension BasicViewController: PanModalPresentable {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
var panScrollable: UIScrollView? {
return nil
}

View File

@ -1,73 +0,0 @@
//
// FullScreenNavController.swift
// PanModalDemo
//
// Created by Stephen Sowole on 5/2/19.
// Copyright © 2019 Detail. All rights reserved.
//
import UIKit
class FullScreenNavController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
pushViewController(FullScreenViewController(), animated: false)
}
}
extension FullScreenNavController: PanModalPresentable {
var panScrollable: UIScrollView? {
return nil
}
var topOffset: CGFloat {
return 0.0
}
var springDamping: CGFloat {
return 1.0
}
var transitionDuration: Double {
return 0.4
}
var transitionAnimationOptions: UIView.AnimationOptions {
return [.allowUserInteraction, .beginFromCurrentState]
}
var shouldRoundTopCorners: Bool {
return false
}
var showDragIndicator: Bool {
return false
}
}
private class FullScreenViewController: UIViewController {
let textLabel: UILabel = {
let label = UILabel()
label.text = "Drag downwards to dismiss"
label.font = UIFont(name: "Lato-Bold", size: 17)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Full Screen"
view.backgroundColor = .white
setupConstraints()
}
private func setupConstraints() {
view.addSubview(textLabel)
textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}

View File

@ -12,17 +12,9 @@ class NavigationController: UINavigationController, PanModalPresentable {
private let navGroups = NavUserGroups()
init() {
super.init(nibName: nil, bundle: nil)
viewControllers = [navGroups]
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
override func viewDidLoad() {
super.viewDidLoad()
pushViewController(navGroups, animated: false)
}
override func popViewController(animated: Bool) -> UIViewController? {

View File

@ -14,10 +14,6 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
let presentable: UserGroupMemberPresentable
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// MARK: - Views
let avatarView: UIView = {
@ -89,7 +85,6 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
roleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4.0).isActive = true
bottomLayoutGuide.topAnchor.constraint(greaterThanOrEqualTo: roleLabel.bottomAnchor).isActive = true
}
// MARK: - Pan Modal Presentable
@ -99,7 +94,7 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
}
var longFormHeight: PanModalHeight {
return .intrinsicHeight
return .contentHeight(300)
}
var anchorModalToLongForm: Bool {

View File

@ -8,7 +8,7 @@
import UIKit
class UserGroupViewController: UITableViewController, PanModalPresentable {
class UserGroupViewController: UITableViewController, PanModalPresentable, UIGestureRecognizerDelegate {
let members: [UserGroupMemberPresentable] = [
UserGroupMemberPresentable(name: "Naida Schill ✈️", role: "Staff Engineer - Mobile DevXP", avatarBackgroundColor: #colorLiteral(red: 0.7215686275, green: 0.9098039216, blue: 0.5607843137, alpha: 1)),
@ -34,10 +34,6 @@ class UserGroupViewController: UITableViewController, PanModalPresentable {
var isShortFormEnabled = true
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
let headerView = UserGroupHeaderView()
let headerPresentable = UserGroupHeaderPresentable.init(handle: "ios-engs", description: "iOS Engineers", memberCount: 10)

View File

@ -48,21 +48,17 @@ class PanModalTests: XCTestCase {
XCTAssertEqual(vc.shortFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.longFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.springDamping, 0.8)
XCTAssertEqual(vc.panModalBackgroundColor, UIColor.black.withAlphaComponent(0.7))
XCTAssertEqual(vc.dragIndicatorBackgroundColor, UIColor.lightGray)
XCTAssertEqual(vc.backgroundAlpha, 0.7)
XCTAssertEqual(vc.scrollIndicatorInsets, .zero)
XCTAssertEqual(vc.anchorModalToLongForm, true)
XCTAssertEqual(vc.allowsExtendedPanScrolling, false)
XCTAssertEqual(vc.allowsDragToDismiss, true)
XCTAssertEqual(vc.allowsTapToDismiss, true)
XCTAssertEqual(vc.isPanScrollEnabled, true)
XCTAssertEqual(vc.isUserInteractionEnabled, true)
XCTAssertEqual(vc.isHapticFeedbackEnabled, true)
XCTAssertEqual(vc.shouldRoundTopCorners, false)
XCTAssertEqual(vc.showDragIndicator, false)
XCTAssertEqual(vc.shouldRoundTopCorners, false)
XCTAssertEqual(vc.cornerRadius, 8.0)
XCTAssertEqual(vc.transitionDuration, PanModalAnimator.Constants.defaultTransitionDuration)
XCTAssertEqual(vc.transitionAnimationOptions, [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState])
}
func testPresentableYValues() {