Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| debeca1fb2 | |||
| 9da54f9fc1 | |||
| a13d053867 | |||
| eda7201fe8 | |||
| b1edef49a4 | |||
| a2db94a8c4 | |||
| 57495cff84 | |||
| eff5cde844 | |||
| c365eadf1e | |||
| 5d02681b05 | |||
| 6e17ff734a | |||
| 02ed923e7b | |||
| 421335d98c | |||
| 2618f49556 | |||
| 328116600f | |||
| a10b1426cd | |||
| 5468856a93 | |||
| 8f3a7de321 | |||
| 2de1fb9ac8 | |||
| 27a2d81a71 | |||
| 85ed3a6ce3 | |||
| b34f1093de | |||
| de1dbe70de | |||
| c593c646ca | |||
| 0cb5307a61 | |||
| 8ab3b7986c | |||
| bbdf3e7c6f | |||
| bdb756b665 | |||
| 6611ec83a2 | |||
| a917d6a626 | |||
| 7511ce577d | |||
| e7d0a72440 | |||
| 44923ef66e | |||
| 2760bc7298 | |||
| d428e96b03 | |||
| 67495961e5 | |||
| 7b88703e43 | |||
| c40e66ef3d | |||
| 53fb131d0e | |||
| 37969f6cb3 | |||
| a0ba3af012 | |||
| 6082dba6a7 | |||
| 43327e8123 | |||
| 0a5bc19d0f | |||
| dd52e1eaee | |||
| 22aa055843 | |||
| 448fc5cbb4 | |||
| b4fe3b408c | |||
| 6ecc7924ba | |||
| 0b82902233 | |||
| 597dbd4145 | |||
| 1ee949ff1b |
@@ -0,0 +1,21 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
test-ios14_5-iPhone_12_Pro:
|
||||
macos:
|
||||
xcode: 13.4.1
|
||||
steps:
|
||||
- checkout
|
||||
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
|
||||
test-ios13_7-iPhone_11_Pro:
|
||||
macos:
|
||||
xcode: 12.5.1
|
||||
steps:
|
||||
- checkout
|
||||
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro'
|
||||
|
||||
workflows:
|
||||
test:
|
||||
jobs:
|
||||
- test-ios14_5-iPhone_12_Pro
|
||||
- test-ios13_7-iPhone_11_Pro
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
@@ -17,6 +18,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.8"
|
||||
xcode: "14.3"
|
||||
runsOn: macos-13
|
||||
- swift: "5.7"
|
||||
xcode: "14.1"
|
||||
runsOn: macos-12
|
||||
@@ -48,34 +52,19 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "16.1"
|
||||
xcode: "14.1"
|
||||
- os: "16.4"
|
||||
xcode: "14.3.1"
|
||||
sim: "iPhone 14 Pro"
|
||||
runsOn: macos-12
|
||||
runsOn: macos-13
|
||||
- os: "15.5"
|
||||
xcode: "13.4.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-12
|
||||
- os: "15.2"
|
||||
xcode: "13.2.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.5"
|
||||
xcode: "12.5.1"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.4"
|
||||
xcode: "12.4"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "13.7"
|
||||
xcode: "11.7"
|
||||
sim: "iPhone 11 Pro"
|
||||
runsOn: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Testing in iOS ${{ matrix.os }}
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}'
|
||||
timeout-minutes: 20
|
||||
|
||||
example:
|
||||
runs-on: macos-12
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FloatingPanel]
|
||||
platform: ios
|
||||
@@ -319,7 +319,7 @@ class SearchPaneliPadBehavior: FloatingPanelBehavior {
|
||||
var momentumProjectionRate: CGFloat {
|
||||
return UIScrollView.DecelerationRate.fast.rawValue
|
||||
}
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
545DB9F321511E6300CA77B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F121511E6300CA77B8 /* Main.storyboard */; };
|
||||
545DB9F521511E6400CA77B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F421511E6400CA77B8 /* Assets.xcassets */; };
|
||||
545DB9F821511E6400CA77B8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */; };
|
||||
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0221511E6400CA77B8 /* SampleTests.swift */; };
|
||||
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0D21511E6400CA77B8 /* SampleUITests.swift */; };
|
||||
546341A125C6415100CA0596 /* UseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341A025C6415100CA0596 /* UseCase.swift */; };
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341AB25C6426500CA0596 /* CustomState.swift */; };
|
||||
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; };
|
||||
@@ -37,23 +35,6 @@
|
||||
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
545DB9FF21511E6400CA77B8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 545DB9E221511E6300CA77B8 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 545DB9E921511E6300CA77B8;
|
||||
remoteInfo = FloatingModalSample;
|
||||
};
|
||||
545DBA0A21511E6400CA77B8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 545DB9E221511E6300CA77B8 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 545DB9E921511E6300CA77B8;
|
||||
remoteInfo = FloatingModalSample;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
549D23CD233C7779008EF4D7 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@@ -88,12 +69,6 @@
|
||||
545DB9F421511E6400CA77B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
545DB9F721511E6400CA77B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
545DB9F921511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DBA0221511E6400CA77B8 /* SampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleTests.swift; sourceTree = "<group>"; };
|
||||
545DBA0421511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = "<group>"; };
|
||||
545DBA0F21511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
546341A025C6415100CA0596 /* UseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCase.swift; sourceTree = "<group>"; };
|
||||
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; sourceTree = "<group>"; };
|
||||
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -114,20 +89,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DB9FB21511E6400CA77B8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DBA0621511E6400CA77B8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -154,8 +115,6 @@
|
||||
children = (
|
||||
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */,
|
||||
545DB9EC21511E6300CA77B8 /* Sources */,
|
||||
545DBA0121511E6400CA77B8 /* Tests */,
|
||||
545DBA0C21511E6400CA77B8 /* UITests */,
|
||||
545DB9EB21511E6300CA77B8 /* Products */,
|
||||
5D82A6AB28D18438006A44BA /* Frameworks */,
|
||||
);
|
||||
@@ -165,8 +124,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9EA21511E6300CA77B8 /* Samples.app */,
|
||||
545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */,
|
||||
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -190,24 +147,6 @@
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545DBA0121511E6400CA77B8 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DBA0221511E6400CA77B8 /* SampleTests.swift */,
|
||||
545DBA0421511E6400CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
545DBA0C21511E6400CA77B8 /* UITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */,
|
||||
545DBA0F21511E6400CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = UITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
546341AA25C6421000CA0596 /* UseCases */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -247,42 +186,6 @@
|
||||
productReference = 545DB9EA21511E6300CA77B8 /* Samples.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
545DB9FD21511E6400CA77B8 /* SamplesTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545DBA1521511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesTests" */;
|
||||
buildPhases = (
|
||||
545DB9FA21511E6400CA77B8 /* Sources */,
|
||||
545DB9FB21511E6400CA77B8 /* Frameworks */,
|
||||
545DB9FC21511E6400CA77B8 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
545DBA0021511E6400CA77B8 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SamplesTests;
|
||||
productName = FloatingModalSampleTests;
|
||||
productReference = 545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
545DBA0821511E6400CA77B8 /* SamplesUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545DBA1821511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesUITests" */;
|
||||
buildPhases = (
|
||||
545DBA0521511E6400CA77B8 /* Sources */,
|
||||
545DBA0621511E6400CA77B8 /* Frameworks */,
|
||||
545DBA0721511E6400CA77B8 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
545DBA0B21511E6400CA77B8 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SamplesUITests;
|
||||
productName = FloatingModalSampleUITests;
|
||||
productReference = 545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -296,14 +199,6 @@
|
||||
545DB9E921511E6300CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
};
|
||||
545DB9FD21511E6400CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
TestTargetID = 545DB9E921511E6300CA77B8;
|
||||
};
|
||||
545DBA0821511E6400CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
TestTargetID = 545DB9E921511E6300CA77B8;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 545DB9E521511E6300CA77B8 /* Build configuration list for PBXProject "Samples" */;
|
||||
@@ -320,8 +215,6 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
545DB9E921511E6300CA77B8 /* Samples */,
|
||||
545DB9FD21511E6400CA77B8 /* SamplesTests */,
|
||||
545DBA0821511E6400CA77B8 /* SamplesUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -337,20 +230,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DB9FC21511E6400CA77B8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DBA0721511E6400CA77B8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -381,37 +260,8 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DB9FA21511E6400CA77B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
545DBA0521511E6400CA77B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
545DBA0021511E6400CA77B8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 545DB9E921511E6300CA77B8 /* Samples */;
|
||||
targetProxy = 545DB9FF21511E6400CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
545DBA0B21511E6400CA77B8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 545DB9E921511E6300CA77B8 /* Samples */;
|
||||
targetProxy = 545DBA0A21511E6400CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
545DB9F121511E6300CA77B8 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -596,90 +446,6 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
545DBA1621511E6400CA77B8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Samples.app/Samples";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
545DBA1721511E6400CA77B8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Samples.app/Samples";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
545DBA1921511E6400CA77B8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = J3D7L9FHSS;
|
||||
INFOPLIST_FILE = UITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = FloatingModalSample;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
545DBA1A21511E6400CA77B8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = J3D7L9FHSS;
|
||||
INFOPLIST_FILE = UITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = FloatingModalSample;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -701,24 +467,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
545DBA1521511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DBA1621511E6400CA77B8 /* Debug */,
|
||||
545DBA1721511E6400CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
545DBA1821511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DBA1921511E6400CA77B8 /* Debug */,
|
||||
545DBA1A21511E6400CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 545DB9E221511E6300CA77B8 /* Project object */;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -383,22 +383,22 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="720"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="720" width="375" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="768" width="375" height="0.0"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="MSC-ch-YJK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
|
||||
<rect key="frame" x="134.5" y="136" width="106" height="326"/>
|
||||
<rect key="frame" x="134.5" y="88" width="106" height="326"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
|
||||
@@ -598,7 +598,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="744"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
@@ -761,14 +761,33 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wUo-kb-NIn">
|
||||
<rect key="frame" x="159" y="16" width="200" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Expand top margin" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OC3-od-ldC">
|
||||
<rect key="frame" x="0.0" y="5.5" width="143" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="XFC-sq-pWj">
|
||||
<rect key="frame" x="151" y="0.0" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTopMargin:" destination="tvD-nO-QUb" eventType="valueChanged" id="XWo-eX-0Jn"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstItem="5ET-zC-lCb" firstAttribute="trailing" secondItem="wUo-kb-NIn" secondAttribute="trailing" constant="16" id="CtG-H5-tAI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
<constraint firstItem="wUo-kb-NIn" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="16" id="ogC-1W-upw"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
@@ -780,7 +799,7 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
|
||||
</scene>
|
||||
<!--Adaptive Layout Test View Controller-->
|
||||
<scene sceneID="rDI-lU-wEx">
|
||||
|
||||
@@ -151,7 +151,7 @@ class DebugTableViewController: InspectableViewController {
|
||||
}
|
||||
}
|
||||
// Set titles
|
||||
tracksScrollView = false
|
||||
tracksScrollView = true
|
||||
followsScrollViewBouncing = false
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,14 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func toggleTopMargin(_ sender: UISwitch) {
|
||||
if sender.isOn {
|
||||
textViewTopConstraint.constant = 160
|
||||
} else {
|
||||
textViewTopConstraint.constant = 16
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func close(sender: UIButton) {
|
||||
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
|
||||
dismiss(animated: true, completion: nil)
|
||||
|
||||
@@ -10,6 +10,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
@@ -34,6 +35,7 @@ extension UseCase {
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
@@ -65,6 +67,7 @@ extension UseCase {
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showOnWindow: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
|
||||
@@ -11,6 +11,7 @@ final class UseCaseController: NSObject {
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
private lazy var overWindowPanelVC = FloatingPanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
@@ -157,6 +158,20 @@ extension UseCaseController {
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showOnWindow:
|
||||
let fpc = overWindowPanelVC
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
|
||||
|
||||
window.addSubview(fpc.view)
|
||||
fpc.view.frame = window.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
fpc.show(animated: true)
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
|
||||
@@ -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>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanelSample
|
||||
|
||||
class SampleTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
class SampleUITests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
|
||||
XCUIApplication().launch()
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// Use recording to get started writing UI tests.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
545BA71421BA3217007F7846 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA71321BA3217007F7846 /* main.m */; };
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; };
|
||||
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -53,7 +52,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.6.1"
|
||||
s.version = "2.7.0"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
|
||||
@@ -17,17 +17,16 @@
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* Controller.swift */; };
|
||||
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberView.swift */; };
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C722C49A8F00D17955 /* TestSupports.swift */; };
|
||||
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */; };
|
||||
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4A024B003EF00537F8A /* AppDelegate.swift */; };
|
||||
5469F4AE24B30D7E00537F8A /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AD24B30D7E00537F8A /* State.swift */; };
|
||||
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
|
||||
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
|
||||
5469F4B424B30F3500537F8A /* LayoutProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutProperties.swift */; };
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F7A9B2A6E946000303905 /* GestureTests.swift */; };
|
||||
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* ExtensionTests.swift */; };
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logging.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
|
||||
@@ -45,13 +44,6 @@
|
||||
remoteGlobalIDString = 545DB9C02151169500CA77B8;
|
||||
remoteInfo = FloatingModalController;
|
||||
};
|
||||
54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 54E740C9218AFD67005C1A34;
|
||||
remoteInfo = TestingHost;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -68,25 +60,22 @@
|
||||
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9DF21511AC100CA77B8 /* Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Controller.swift; sourceTree = "<group>"; };
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberView.swift; sourceTree = "<group>"; };
|
||||
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
5469F4A024B003EF00537F8A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5469F4A124B003EF00537F8A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5469F4AD24B30D7E00537F8A /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = "<group>"; };
|
||||
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
|
||||
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
|
||||
5469F4B324B30F3500537F8A /* LayoutProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutProperties.swift; sourceTree = "<group>"; };
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureTests.swift; sourceTree = "<group>"; };
|
||||
549C371E2361E15D007D8058 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = "<group>"; };
|
||||
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
|
||||
54CFBFC4215CD09C006B5735 /* Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = "<group>"; };
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FloatingPanel.docc; sourceTree = "<group>"; };
|
||||
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTesting.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -107,13 +96,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C7218AFD67005C1A34 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -132,7 +114,6 @@
|
||||
children = (
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */,
|
||||
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */,
|
||||
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -154,7 +135,7 @@
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
|
||||
@@ -165,9 +146,9 @@
|
||||
545DB9CE2151169500CA77B8 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5469F49E24B003EF00537F8A /* TestingApp */,
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */,
|
||||
542753C522C49A6E00D17955 /* LayoutTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
|
||||
549E944422CF295D0050AECF /* StateTests.swift */,
|
||||
@@ -178,16 +159,6 @@
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5469F49E24B003EF00537F8A /* TestingApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */,
|
||||
5469F4A024B003EF00537F8A /* AppDelegate.swift */,
|
||||
5469F4A124B003EF00537F8A /* Info.plist */,
|
||||
);
|
||||
path = TestingApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D82A6B328D18460006A44BA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -240,30 +211,12 @@
|
||||
);
|
||||
dependencies = (
|
||||
545DB9CD2151169500CA77B8 /* PBXTargetDependency */,
|
||||
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */,
|
||||
);
|
||||
name = FloatingPanelTests;
|
||||
productName = FloatingModalControllerTests;
|
||||
productReference = 545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
54E740C9218AFD67005C1A34 /* TestingApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */;
|
||||
buildPhases = (
|
||||
54E740C6218AFD67005C1A34 /* Sources */,
|
||||
54E740C7218AFD67005C1A34 /* Frameworks */,
|
||||
54E740C8218AFD67005C1A34 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = TestingApp;
|
||||
productName = TestingHost;
|
||||
productReference = 54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -280,10 +233,6 @@
|
||||
};
|
||||
545DB9C92151169500CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
TestTargetID = 54E740C9218AFD67005C1A34;
|
||||
};
|
||||
54E740C9218AFD67005C1A34 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -302,7 +251,6 @@
|
||||
targets = (
|
||||
545DB9C02151169500CA77B8 /* FloatingPanel */,
|
||||
545DB9C92151169500CA77B8 /* FloatingPanelTests */,
|
||||
54E740C9218AFD67005C1A34 /* TestingApp */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -322,14 +270,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C8218AFD67005C1A34 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -345,7 +285,7 @@
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
|
||||
@@ -367,14 +307,7 @@
|
||||
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */,
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C6218AFD67005C1A34 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */,
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -386,11 +319,6 @@
|
||||
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
|
||||
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 54E740C9218AFD67005C1A34 /* TestingApp */;
|
||||
targetProxy = 54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -587,17 +515,16 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -608,53 +535,16 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
54E740DA218AFD6A005C1A34 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
54E740DB218AFD6A005C1A34 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -761,31 +651,16 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E8AC6A2286CFB6000C5A12 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
@@ -822,16 +697,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
54E740DA218AFD6A005C1A34 /* Debug */,
|
||||
54E740DB218AFD6A005C1A34 /* Release */,
|
||||
54E8AC6A2286CFB6000C5A12 /* Test */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
[](https://travis-ci.org/SCENEE/FloatingPanel)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://swift.org/)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://swift.org/)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||

|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
# FloatingPanel
|
||||
|
||||
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
|
||||
The user interface displays related content and utilities alongside the main content.
|
||||
|
||||
📝[Here](https://docs.scenee.com/documentation/floatingpanel) is the API references for the latest version powered by [DocC](https://developer.apple.com/documentation/docc).
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.7.0/documentation/floatingpanel/) for more details.
|
||||
|
||||

|
||||

|
||||
@@ -75,14 +75,14 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [x] Removal interaction
|
||||
- [x] Multi panel support
|
||||
- [x] Modal presentation
|
||||
- [x] 4 positioning support(top, left, bottom, right)
|
||||
- [x] Support for 4 positions (top, left, bottom, right)
|
||||
- [x] 1 or more magnetic anchors(full, half, tip and more)
|
||||
- [x] Layout support for all trait environments(i.e. Landscape orientation)
|
||||
- [x] Common UI elements: surface, backdrop and grabber handle
|
||||
- [x] Free from common issues of Auto Layout and gesture handling
|
||||
- [x] Free from common Auto Layout and gesture handling issues
|
||||
- [x] Compatible with Objective-C
|
||||
|
||||
Examples are here.
|
||||
Examples can be found here:
|
||||
|
||||
- [Examples/Maps](https://github.com/SCENEE/FloatingPanel/tree/master/Examples/Maps) like Apple Maps.app.
|
||||
- [Examples/Stocks](https://github.com/SCENEE/FloatingPanel/tree/master/Examples/Stocks) like Apple Stocks.app.
|
||||
@@ -91,11 +91,11 @@ Examples are here.
|
||||
|
||||
## Requirements
|
||||
|
||||
FloatingPanel is written in Swift 5.0+. Compatible with iOS 11.0+.
|
||||
FloatingPanel is written in Swift 5.0+ and compatible with iOS 11.0+.
|
||||
|
||||
The deployment is still iOS 10, but it is recommended to use this library on iOS 11+.
|
||||
While it still supports iOS 10, it is recommended to use this library on iOS 11+.
|
||||
|
||||
:pencil2: You would like to use Swift 4.0. Please use FloatingPanel v1.
|
||||
:pencil2: If you'd like to use Swift 4.0, please use FloatingPanel v1.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -425,7 +425,7 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
class CustomPanelBehavior: FloatingPanelBehavior {
|
||||
let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02
|
||||
let springResponseTime = 0.4
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -451,7 +451,7 @@ This allows full projectional panel behavior. For example, a user can swipe up a
|
||||
```swift
|
||||
class MyPanelBehavior: FloatingPanelBehavior {
|
||||
...
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelPosition) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelPosition) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ public protocol FloatingPanelBehavior {
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelState.tip` or `FloatingPanelState.full`.
|
||||
/// Therefore, `proposedState` can only be `FloatingPanelState.tip` or `FloatingPanelState.full`.
|
||||
@objc optional
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
|
||||
+38
-23
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
|
||||
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
|
||||
@@ -51,7 +52,12 @@ import UIKit
|
||||
|
||||
/// Called on finger up if the user dragged.
|
||||
///
|
||||
/// If `attract` is true, it will continue moving afterwards to a nearby state anchor.
|
||||
/// If `attract` is true, the panel continues moving towards the nearby state
|
||||
/// anchor. Otherwise, it stops at the closest state anchor.
|
||||
///
|
||||
/// - Note: If `attract` is false, ``FloatingPanelController.state`` property has
|
||||
/// already changed to the closest anchor's state by the time this delegate method
|
||||
/// is called.
|
||||
@objc optional
|
||||
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
|
||||
@@ -167,7 +173,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_layout = newValue
|
||||
if let parent = parent, let layout = newValue as? UIViewController, layout == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its layout object. Don't allow the parent to adopt FloatingPanelLayout."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +186,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_behavior = newValue
|
||||
if let parent = parent, let behavior = newValue as? UIViewController, behavior == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its behavior object. Don't allow the parent to adopt FloatingPanelBehavior."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +221,7 @@ open class FloatingPanelController: UIViewController {
|
||||
/// The NearbyState determines that finger's nearby state.
|
||||
public var nearbyState: FloatingPanelState {
|
||||
let currentY = surfaceLocation.y
|
||||
return floatingPanel.targetPosition(from: currentY, with: .zero)
|
||||
return floatingPanel.targetState(from: currentY, with: .zero)
|
||||
}
|
||||
|
||||
/// Constants that define how a panel content fills in the surface.
|
||||
@@ -375,7 +383,7 @@ open class FloatingPanelController: UIViewController {
|
||||
preSafeAreaInsets != safeAreaInsets
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
|
||||
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
@@ -414,8 +422,15 @@ open class FloatingPanelController: UIViewController {
|
||||
guard let self = self else { return }
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
} else {
|
||||
} else if parent != nil {
|
||||
removePanelFromParent(animated: true)
|
||||
} else {
|
||||
delegate?.floatingPanelWillRemove?(self)
|
||||
hide(animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +491,7 @@ open class FloatingPanelController: UIViewController {
|
||||
@objc(addPanelToParent:at:animated:completion:)
|
||||
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
guard self.parent == nil else {
|
||||
log.warning("Already added to a parent(\(parent))")
|
||||
os_log(msg, log: sysLog, type: .error, "Warning: already added to a parent(\(parent))")
|
||||
return
|
||||
}
|
||||
assert((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
|
||||
@@ -692,30 +707,30 @@ extension FloatingPanelController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Swizzling
|
||||
|
||||
private var originalDismissImp: IMP?
|
||||
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Void = {
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
if let imp = class_getMethodImplementation(aClass, #selector(dismiss(animated:completion:))),
|
||||
let originalAltMethod = class_getInstanceMethod(aClass, #selector(fp_original_dismiss(animated:completion:))) {
|
||||
method_setImplementation(originalAltMethod, imp)
|
||||
}
|
||||
let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:)))
|
||||
let swizzledMethod = class_getInstanceMethod(aClass, #selector(fp_dismiss(animated:completion:)))
|
||||
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
if let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))),
|
||||
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:))) {
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
|
||||
}
|
||||
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
extension UIViewController {
|
||||
@objc
|
||||
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
|
||||
let sel = #selector(UIViewController.dismiss(animated:completion:))
|
||||
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
if let fpc = parent as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
@@ -725,7 +740,7 @@ public extension UIViewController {
|
||||
if let fpc = self as? FloatingPanelController {
|
||||
// When a panel is presented modally and it's not a child view controller of the presented view controller.
|
||||
if fpc.presentingViewController != nil, fpc.parent == nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
@@ -733,6 +748,6 @@ public extension UIViewController {
|
||||
}
|
||||
|
||||
// For other view controllers
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
}
|
||||
}
|
||||
|
||||
+230
-150
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
///
|
||||
/// The presentation model of FloatingPanel
|
||||
@@ -35,7 +36,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
private(set) var state: FloatingPanelState = .hidden {
|
||||
didSet {
|
||||
log.debug("state changed: \(oldValue) -> \(state)")
|
||||
os_log(msg, log: devLog, type: .debug, "state changed: \(oldValue) -> \(state)")
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidChangeState?(vc)
|
||||
}
|
||||
@@ -43,6 +44,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
let panGestureDelegateRouter: FloatingPanelPanGestureRecognizer.DelegateRouter
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
|
||||
@@ -63,9 +65,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
// Scroll handling
|
||||
private var initialScrollOffset: CGPoint = .zero
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
private var scrollBounce = false
|
||||
private var scrollIndictorVisible = false
|
||||
private var scrollBounceThreshold: CGFloat = -30.0
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
@@ -84,17 +86,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
behaviorAdapter = BehaviorAdapter(vc: vc, behavior: behavior)
|
||||
|
||||
panGestureRecognizer = FloatingPanelPanGestureRecognizer()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
panGestureRecognizer.name = "FloatingPanelPanGestureRecognizer"
|
||||
}
|
||||
panGestureDelegateRouter = FloatingPanelPanGestureRecognizer.DelegateRouter(panGestureRecognizer: panGestureRecognizer)
|
||||
|
||||
super.init()
|
||||
|
||||
panGestureRecognizer.floatingPanel = self
|
||||
panGestureRecognizer.set(floatingPanel: self)
|
||||
surfaceView.addGestureRecognizer(panGestureRecognizer)
|
||||
panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
panGestureRecognizer.delegate = self
|
||||
|
||||
// Assign the delegate router to `FloatingPanelPanGestureRecognizer.delegate` only after setting
|
||||
// `FloatingPanelPanGestureRecognizer.floatingPanel` property.
|
||||
// This is because `delegateOrigin` is used at the time of assignment to its `delegate` property
|
||||
// through the delegate router.
|
||||
panGestureRecognizer.delegate = panGestureDelegateRouter
|
||||
|
||||
// Set the tap-to-dismiss action of the backdrop view.
|
||||
// It's disabled by default. See also BackdropView.dismissalTapGestureRecognizer.
|
||||
@@ -126,7 +130,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if animated {
|
||||
let updateScrollView: () -> Void = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.state == self.layoutAdapter.mostExpandedState, abs(self.layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
if self.state == self.layoutAdapter.mostExpandedState, 0 == self.layoutAdapter.offsetFromMostExpandedAnchor {
|
||||
self.unlockScrollView()
|
||||
} else {
|
||||
self.lockScrollView()
|
||||
@@ -141,10 +145,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let animationVector = CGVector(dx: abs(removalVector.dx), dy: abs(removalVector.dy))
|
||||
animator = vc.animatorForDismissing(with: animationVector)
|
||||
default:
|
||||
move(to: to, with: 0) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.moveAnimator = nil
|
||||
startAttraction(to: to, with: .zero) { [weak self] in
|
||||
self?.endAttraction(false)
|
||||
updateScrollView()
|
||||
completion?()
|
||||
}
|
||||
@@ -162,7 +164,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
self.updateLayout(to: to)
|
||||
|
||||
if shouldDoubleLayout {
|
||||
log.info("Lay out the surface again to modify an intrinsic size error according to UIStackView")
|
||||
os_log(msg, log: sysLog, type: .info, "Lay out the surface again to modify an intrinsic size error according to UIStackView")
|
||||
self.updateLayout(to: to)
|
||||
}
|
||||
}
|
||||
@@ -231,7 +233,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func getBackdropAlpha(at cur: CGFloat, with translation: CGFloat) -> CGFloat {
|
||||
/* log.debug("currentY: \(currentY) translation: \(translation)") */
|
||||
/* os_log(msg, log: devLog, type: .debug, "currentY: \(currentY) translation: \(translation)") */
|
||||
let forwardY = (translation >= 0)
|
||||
|
||||
let segment = layoutAdapter.segment(at: cur, forward: forwardY)
|
||||
@@ -258,13 +260,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let result = panGestureRecognizer.delegateProxy?.gestureRecognizer?(gestureRecognizer, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) {
|
||||
return result
|
||||
}
|
||||
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
|
||||
/* os_log(msg, log: devLog, type: .debug, "shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is FloatingPanelPanGestureRecognizer:
|
||||
@@ -289,10 +287,6 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let result = panGestureRecognizer.delegateProxy?.gestureRecognizer?(gestureRecognizer, shouldBeRequiredToFailBy: otherGestureRecognizer) {
|
||||
return result
|
||||
}
|
||||
|
||||
if otherGestureRecognizer is FloatingPanelPanGestureRecognizer {
|
||||
// If this panel is the farthest descendant of visible panels,
|
||||
// its ancestors' pan gesture must wait for its pan gesture to fail
|
||||
@@ -314,10 +308,6 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let result = panGestureRecognizer.delegateProxy?.gestureRecognizer?(gestureRecognizer, shouldRequireFailureOf: otherGestureRecognizer) {
|
||||
return result
|
||||
}
|
||||
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
// Should begin the pan gesture without waiting for the tracking scroll view's gestures.
|
||||
@@ -337,7 +327,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return false
|
||||
}
|
||||
return allowScrollPanGesture(for: scrollView)
|
||||
guard state == layoutAdapter.mostExpandedState else { return false }
|
||||
// The condition where offset > 0 must not be included here. Because it will stop recognizing
|
||||
// the panel pan gesture if a user starts scrolling content from an offset greater than 0.
|
||||
return allowScrollPanGesture(of: scrollView) { offset in offset <= scrollBounceThreshold }
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -388,23 +381,24 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let velocity = value(of: panGesture.velocity(in: panGesture.view))
|
||||
let location = panGesture.location(in: surfaceView)
|
||||
|
||||
let belowEdgeMost = 0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)
|
||||
let insideMostExpandedAnchor = 0 > layoutAdapter.offsetFromMostExpandedAnchor
|
||||
|
||||
log.debug("""
|
||||
os_log(msg, log: devLog, type: .debug, """
|
||||
scroll gesture(\(state):\(panGesture.state)) -- \
|
||||
belowTop = \(belowEdgeMost), \
|
||||
inside expanded anchor = \(insideMostExpandedAnchor), \
|
||||
interactionInProgress = \(interactionInProgress), \
|
||||
scroll offset = \(value(of: scrollView.contentOffset)), \
|
||||
location = \(value(of: location)), velocity = \(velocity)
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
let offsetDiff = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
|
||||
if belowEdgeMost {
|
||||
if insideMostExpandedAnchor {
|
||||
// Scroll offset pinning
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
if interactionInProgress {
|
||||
log.debug("settle offset --", value(of: initialScrollOffset))
|
||||
os_log(msg, log: devLog, type: .debug, "settle offset -- \(value(of: initialScrollOffset))")
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
} else {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
@@ -456,21 +450,24 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
} else {
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
let allowScroll = allowScrollPanGesture(of: scrollView) { offset in
|
||||
offset <= scrollBounceThreshold || 0 < offset
|
||||
}
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
if velocity < 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
if velocity < 0, !allowScroll {
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
if velocity > 0, allowScrollPanGesture(for: scrollView) {
|
||||
if velocity > 0, allowScroll {
|
||||
unlockScrollView()
|
||||
}
|
||||
case .bottom, .right:
|
||||
// Hide a scroll indicator just before starting an interaction by swiping a panel down.
|
||||
if velocity > 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
if velocity > 0, !allowScroll {
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
// Show a scroll indicator when an animation is interrupted at the top and content is scrolled up
|
||||
if velocity < 0, allowScrollPanGesture(for: scrollView) {
|
||||
if velocity < 0, allowScroll {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
@@ -486,7 +483,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let velocity = panGesture.velocity(in: panGesture.view)
|
||||
let location = panGesture.location(in: panGesture.view)
|
||||
|
||||
log.debug("""
|
||||
os_log(msg, log: devLog, type: .debug, """
|
||||
panel gesture(\(state):\(panGesture.state)) -- \
|
||||
translation = \(value(of: translation)), \
|
||||
location = \(value(of: location)), \
|
||||
@@ -536,19 +533,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
private func interruptAnimationIfNeeded() {
|
||||
if let animator = self.moveAnimator, animator.isRunning {
|
||||
log.debug("the attraction animator interrupted!!!")
|
||||
os_log(msg, log: devLog, type: .debug, "the attraction animator interrupted!!!")
|
||||
animator.stopAnimation(true)
|
||||
endAttraction(false)
|
||||
}
|
||||
if let animator = self.transitionAnimator {
|
||||
guard 0 >= layoutAdapter.offsetFromMostExpandedAnchor else { return }
|
||||
log.debug("a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
|
||||
os_log(msg, log: devLog, type: .debug, "a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
// A user can stop a panel at the nearest Y of a target position so this fine-tunes
|
||||
// the a small gap between the presentation layer frame and model layer frame
|
||||
// to unlock scroll view properly at finishAnimation(at:)
|
||||
if abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
if 0 == layoutAdapter.offsetFromMostExpandedAnchor {
|
||||
layoutAdapter.surfaceLocation = layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState)
|
||||
}
|
||||
animator.finishAnimation(at: .current)
|
||||
@@ -634,7 +631,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// A user interaction does not always start from Began state of the pan gesture
|
||||
// because it can be recognized in scrolling a content in a content view controller.
|
||||
// So here just preserve the current state if needed.
|
||||
log.debug("panningBegan -- location = \(value(of: location))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningBegan -- location = \(value(of: location))")
|
||||
|
||||
guard let scrollView = scrollView else { return }
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
@@ -647,7 +644,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func panningChange(with translation: CGPoint) {
|
||||
log.debug("panningChange -- translation = \(value(of: translation))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningChange -- translation = \(value(of: translation))")
|
||||
let pre = value(of: layoutAdapter.surfaceLocation)
|
||||
let diff = value(of: translation - initialTranslation)
|
||||
let next = pre + diff
|
||||
@@ -700,27 +697,18 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
|
||||
log.debug("panningEnd -- translation = \(value(of: translation)), velocity = \(value(of: velocity))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningEnd -- translation = \(value(of: translation)), velocity = \(value(of: velocity))")
|
||||
|
||||
if state == .hidden {
|
||||
log.debug("Already hidden")
|
||||
os_log(msg, log: devLog, type: .debug, "Already hidden")
|
||||
return
|
||||
}
|
||||
|
||||
stopScrollDeceleration = (0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.stopScrolling(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
|
||||
let currentPos = value(of: layoutAdapter.surfaceLocation)
|
||||
let mainVelocity = value(of: velocity)
|
||||
var targetPosition = self.targetPosition(from: currentPos, with: mainVelocity)
|
||||
var target = self.targetState(from: currentPos, with: mainVelocity)
|
||||
|
||||
endInteraction(for: targetPosition)
|
||||
endInteraction(for: target)
|
||||
|
||||
if isRemovalInteractionEnabled {
|
||||
let distToHidden = CGFloat(abs(currentPos - layoutAdapter.position(for: .hidden)))
|
||||
@@ -737,17 +725,18 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelWillEndDragging?(vc, withVelocity: velocity, targetState: &targetPosition)
|
||||
vc.delegate?.floatingPanelWillEndDragging?(vc, withVelocity: velocity, targetState: &target)
|
||||
}
|
||||
|
||||
guard shouldAttract(to: targetPosition) else {
|
||||
guard shouldAttract(to: target) else {
|
||||
self.state = target
|
||||
self.updateLayout(to: target)
|
||||
self.unlockScrollView()
|
||||
// The `floatingPanelDidEndDragging(_:willAttract:)` must be called after the state property changes.
|
||||
// This allows library users to get the correct state in the delegate method.
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: false)
|
||||
}
|
||||
|
||||
self.state = targetPosition
|
||||
self.updateLayout(to: targetPosition)
|
||||
self.unlockScrollView()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -755,18 +744,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: true)
|
||||
}
|
||||
|
||||
// Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating
|
||||
let isScrollEnabled = scrollView?.isScrollEnabled
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState {
|
||||
scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
startAttraction(to: targetPosition, with: velocity)
|
||||
|
||||
// Workaround: Reset `self.scrollView.isScrollEnabled`
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState,
|
||||
let isScrollEnabled = isScrollEnabled {
|
||||
scrollView.isScrollEnabled = isScrollEnabled
|
||||
startAttraction(to: target, with: velocity) { [weak self] in
|
||||
self?.endAttraction(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,24 +771,56 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
private func startInteraction(with translation: CGPoint, at location: CGPoint) {
|
||||
/* Don't lock a scroll view to show a scroll indicator after hitting the top */
|
||||
log.debug("startInteraction -- translation = \(value(of: translation)), location = \(value(of: location))")
|
||||
os_log(msg, log: devLog, type: .debug, "startInteraction -- translation = \(value(of: translation)), location = \(value(of: location))")
|
||||
guard interactionInProgress == false else { return }
|
||||
|
||||
var offset: CGPoint = .zero
|
||||
|
||||
initialSurfaceLocation = layoutAdapter.surfaceLocation
|
||||
if state == layoutAdapter.mostExpandedState, let scrollView = scrollView {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
let scrollFrame = scrollView.convert(scrollView.bounds, to: nil)
|
||||
let touchStartingPoint = surfaceView.convert(initialLocation, to: nil)
|
||||
|
||||
ifLabel: if surfaceView.grabberAreaContains(location) {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
} else {
|
||||
} else if scrollFrame.contains(touchStartingPoint) {
|
||||
let pinningOffset = contentOffsetForPinning(of: scrollView)
|
||||
|
||||
// `scrollView.contentOffset` can be a value in [-30, 0) at this time by `allowScrollPanGesture(for:)`.
|
||||
// Therefore the initial scroll offset must be reset to the pinning offset. Otherwise, the following
|
||||
// `Fit the surface bounds` logic don't working and also the scroll content offset can be invalid.
|
||||
// This code block handles the scenario where there's a navigation bar or toolbar
|
||||
// above the tracking scroll view with corresponding content insets set, and users
|
||||
// move the panel by interacting with these bars. One case of the scenario can be
|
||||
// tested with 'Show Navigation Controller' in Samples.app
|
||||
do {
|
||||
// Adjust the location by subtracting scrollView's origin to reference the frame
|
||||
// rectangle of the scroll view itself.
|
||||
let _location = scrollView.convert(location, from: surfaceView) - scrollView.bounds.origin
|
||||
|
||||
os_log(msg, log: devLog, type: .debug, "startInteraction -- location in scroll view = \(_location))")
|
||||
|
||||
// Keep the scroll content offset if the current touch position is inside its
|
||||
// content inset area.
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
let base = value(of: scrollView.bounds.size)
|
||||
if value(of: pinningOffset) + (base - value(of: _location)) < 0 {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
break ifLabel
|
||||
}
|
||||
case .bottom, .right:
|
||||
if value(of: pinningOffset) + value(of: _location) < 0 {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
break ifLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `initialScrollOffset` must be reset to the pinning offset because the value of `scrollView.contentOffset`,
|
||||
// for instance, is a value in [-30, 0) on a bottom positioned panel with `allowScrollPanGesture(of:condition:)`.
|
||||
// If it's not reset, the following logic to shift the surface frame will not work and then the scroll
|
||||
// content offset will become an unexpected value.
|
||||
initialScrollOffset = pinningOffset
|
||||
|
||||
// Fit the surface bounds to a scroll offset content by startInteraction(at:offset:)
|
||||
// Shift the surface frame to negate the scroll content offset at startInteraction(at:offset:)
|
||||
let offsetDiff = scrollView.contentOffset - pinningOffset
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
@@ -821,8 +832,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
offset = -offsetDiff
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
}
|
||||
log.debug("initial scroll offset --", initialScrollOffset)
|
||||
os_log(msg, log: devLog, type: .debug, "initial scroll offset -- \(initialScrollOffset)")
|
||||
}
|
||||
|
||||
initialTranslation = translation
|
||||
@@ -838,21 +851,21 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
lockScrollView()
|
||||
}
|
||||
|
||||
private func endInteraction(for targetPosition: FloatingPanelState) {
|
||||
log.debug("endInteraction to \(targetPosition)")
|
||||
private func endInteraction(for state: FloatingPanelState) {
|
||||
os_log(msg, log: devLog, type: .debug, "endInteraction to \(state)")
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("endInteraction -- scroll offset = \(scrollView.contentOffset)")
|
||||
os_log(msg, log: devLog, type: .debug, "endInteraction -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
interactionInProgress = false
|
||||
|
||||
// Prevent to keep a scroll view indicator visible at the half/tip position
|
||||
if targetPosition != layoutAdapter.mostExpandedState {
|
||||
if state != layoutAdapter.mostExpandedState {
|
||||
lockScrollView()
|
||||
}
|
||||
|
||||
layoutAdapter.endInteraction(at: targetPosition)
|
||||
layoutAdapter.endInteraction(at: state)
|
||||
}
|
||||
|
||||
private func tearDownActiveInteraction() {
|
||||
@@ -862,26 +875,24 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
panGestureRecognizer.isEnabled = true
|
||||
}
|
||||
|
||||
private func shouldAttract(to targetState: FloatingPanelState) -> Bool {
|
||||
if layoutAdapter.position(for: targetState) == value(of: layoutAdapter.surfaceLocation) {
|
||||
private func shouldAttract(to state: FloatingPanelState) -> Bool {
|
||||
if layoutAdapter.position(for: state) == value(of: layoutAdapter.surfaceLocation) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func startAttraction(to targetPosition: FloatingPanelState, with velocity: CGPoint) {
|
||||
log.debug("startAnimation to \(targetPosition) -- velocity = \(value(of: velocity))")
|
||||
private func startAttraction(to state: FloatingPanelState, with velocity: CGPoint, completion: @escaping (() -> Void)) {
|
||||
os_log(msg, log: devLog, type: .debug, "startAnimation to \(state) -- velocity = \(value(of: velocity))")
|
||||
guard let vc = ownerVC else { return }
|
||||
|
||||
isAttracting = true
|
||||
vc.delegate?.floatingPanelWillBeginAttracting?(vc, to: targetPosition)
|
||||
move(to: targetPosition, with: value(of: velocity)) {
|
||||
self.endAttraction(true)
|
||||
}
|
||||
vc.delegate?.floatingPanelWillBeginAttracting?(vc, to: state)
|
||||
move(to: state, with: value(of: velocity), completion: completion)
|
||||
}
|
||||
|
||||
private func move(to targetPosition: FloatingPanelState, with velocity: CGFloat, completion: @escaping (() -> Void)) {
|
||||
let (animationConstraint, target) = layoutAdapter.setUpAttraction(to: targetPosition)
|
||||
private func move(to state: FloatingPanelState, with velocity: CGFloat, completion: @escaping (() -> Void)) {
|
||||
let (animationConstraint, target) = layoutAdapter.setUpAttraction(to: state)
|
||||
let initialData = NumericSpringAnimator.Data(value: animationConstraint.constant, velocity: velocity)
|
||||
moveAnimator = NumericSpringAnimator(
|
||||
initialData: initialData,
|
||||
@@ -894,22 +905,34 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let ownerVC = self.ownerVC // Ensure the owner vc is existing for `layoutAdapter.surfaceLocation`
|
||||
else { return }
|
||||
animationConstraint.constant = data.value
|
||||
|
||||
let current = self.value(of: self.layoutAdapter.surfaceLocation)
|
||||
let translation = data.value - initialData.value
|
||||
self.backdropView.alpha = self.getBackdropAlpha(at: current, with: translation)
|
||||
|
||||
// Pin the offset of the tracking scroll view while moving by this animator
|
||||
if let scrollView = self.scrollView {
|
||||
self.stopScrolling(at: self.initialScrollOffset)
|
||||
os_log(msg, log: devLog, type: .debug, "move -- pinning scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
ownerVC.notifyDidMove()
|
||||
},
|
||||
completion: { [weak self] in
|
||||
guard let self = self,
|
||||
self.ownerVC != nil else { return }
|
||||
self.updateLayout(to: targetPosition)
|
||||
let ownerVC = self.ownerVC
|
||||
else { return }
|
||||
self.updateLayout(to: state)
|
||||
// Notify when it has reached the target anchor point. At this point, the surface location is equal to
|
||||
// the target anchor location.
|
||||
ownerVC.notifyDidMove()
|
||||
completion()
|
||||
})
|
||||
moveAnimator?.startAnimation()
|
||||
state = targetPosition
|
||||
self.state = state
|
||||
}
|
||||
|
||||
private func endAttraction(_ finished: Bool) {
|
||||
private func endAttraction(_ tryUnlockScroll: Bool) {
|
||||
self.isAttracting = false
|
||||
self.moveAnimator = nil
|
||||
|
||||
@@ -918,18 +941,20 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("finishAnimation -- scroll offset = \(scrollView.contentOffset)")
|
||||
os_log(msg, log: devLog, type: .debug, "finishAnimation -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
stopScrollDeceleration = false
|
||||
|
||||
log.debug("""
|
||||
os_log(msg, log: devLog, type: .debug, """
|
||||
finishAnimation -- state = \(state) \
|
||||
surface location = \(layoutAdapter.surfaceLocation) \
|
||||
edge most position = \(layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState))
|
||||
""")
|
||||
if finished, state == layoutAdapter.mostExpandedState, abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
unlockScrollView()
|
||||
|
||||
if tryUnlockScroll {
|
||||
if (state == layoutAdapter.mostExpandedState && 0 == layoutAdapter.offsetFromMostExpandedAnchor)
|
||||
|| shouldLooselyLockScrollView {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,6 +962,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return layoutAdapter.position.mainLocation(point)
|
||||
}
|
||||
|
||||
func value(of size: CGSize) -> CGFloat {
|
||||
return layoutAdapter.position.mainDimension(size)
|
||||
}
|
||||
|
||||
func setValue(_ newValue: CGPoint, to point: inout CGPoint) {
|
||||
switch layoutAdapter.position {
|
||||
case .top, .bottom:
|
||||
@@ -952,8 +981,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
|
||||
}
|
||||
|
||||
func targetPosition(from currentY: CGFloat, with velocity: CGFloat) -> (FloatingPanelState) {
|
||||
log.debug("targetPosition -- currentY = \(currentY), velocity = \(velocity)")
|
||||
func targetState(from currentY: CGFloat, with velocity: CGFloat) -> FloatingPanelState {
|
||||
os_log(msg, log: devLog, type: .debug, "targetState -- currentY = \(currentY), velocity = \(velocity)")
|
||||
|
||||
let sortedPositions = layoutAdapter.sortedAnchorStatesByCoordinate
|
||||
|
||||
@@ -979,7 +1008,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
(fromPos, toPos) = forwardY ? (lowerPos, upperPos) : (upperPos, lowerPos)
|
||||
|
||||
if behaviorAdapter.shouldProjectMomentum(to: toPos) == false {
|
||||
log.debug("targetPosition -- negate projection: distance = \(distance)")
|
||||
os_log(msg, log: devLog, type: .debug, "targetState -- negate projection: distance = \(distance)")
|
||||
let segment = layoutAdapter.segment(at: currentY, forward: forwardY)
|
||||
var (lowerPos, upperPos) = (segment.lower ?? sortedPositions.first!, segment.upper ?? sortedPositions.last!)
|
||||
// Equate the segment out of {top,bottom} most state to the {top,bottom} most segment
|
||||
@@ -1024,32 +1053,51 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
scrollView.transform = CGAffineTransform(translationX: 0, y: contentOffset)
|
||||
}
|
||||
|
||||
private func lockScrollView() {
|
||||
private func lockScrollView(strict: Bool = false) {
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
if scrollView.isLocked {
|
||||
log.debug("Already scroll locked.")
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll locked.")
|
||||
return
|
||||
}
|
||||
log.debug("lock scroll view")
|
||||
os_log(msg, log: devLog, type: .debug, "lock scroll view")
|
||||
|
||||
scrollBounce = scrollView.bounces
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
|
||||
if !strict, shouldLooselyLockScrollView {
|
||||
// Don't change its `bounces` property. If it's changed, it will cause its scroll content offset jump at
|
||||
// the most expanded anchor position while seamlessly scrolling content. This problem only occurs where its
|
||||
// content mode is `.fitToBounds` and the tracking scroll content is smaller than the content view size.
|
||||
// The reason why is because `bounces` prop change leads to the "content frame" change on `.fitToBounds`.
|
||||
// See also https://github.com/scenee/FloatingPanel/issues/524.
|
||||
} else {
|
||||
scrollBounce = scrollView.bounces
|
||||
scrollView.bounces = false
|
||||
}
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
scrollView.bounces = false
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
}
|
||||
|
||||
private func unlockScrollView() {
|
||||
guard let scrollView = scrollView, scrollView.isLocked else { return }
|
||||
log.debug("unlock scroll view")
|
||||
os_log(msg, log: devLog, type: .debug, "unlock scroll view")
|
||||
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
scrollView.bounces = scrollBounce
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
scrollView.showsVerticalScrollIndicator = scrollIndictorVisible
|
||||
}
|
||||
|
||||
private var shouldLooselyLockScrollView: Bool {
|
||||
var isSmallScrollContentAndFitToBoundsMode: Bool {
|
||||
if ownerVC?.contentMode == .fitToBounds, let scrollView = scrollView,
|
||||
value(of: scrollView.contentSize) < value(of: scrollView.bounds.size) - min(layoutAdapter.offsetFromMostExpandedAnchor, 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return isSmallScrollContentAndFitToBoundsMode
|
||||
}
|
||||
|
||||
private func stopScrolling(at contentOffset: CGPoint) {
|
||||
// Must use setContentOffset(_:animated) to force-stop deceleration
|
||||
guard let scrollView = scrollView else { return }
|
||||
@@ -1074,43 +1122,41 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func allowScrollPanGesture(for scrollView: UIScrollView) -> Bool {
|
||||
guard state == layoutAdapter.mostExpandedState else { return false }
|
||||
var offsetY: CGFloat = 0
|
||||
private func allowScrollPanGesture(of scrollView: UIScrollView, condition: (_ offset: CGFloat) -> Bool) -> Bool {
|
||||
var offset: CGFloat = 0
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
offsetY = value(of: scrollView.fp_contentOffsetMax - scrollView.contentOffset)
|
||||
offset = value(of: scrollView.fp_contentOffsetMax - scrollView.contentOffset)
|
||||
case .bottom, .right:
|
||||
offsetY = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
offset = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
}
|
||||
return offsetY <= -30.0 || offsetY > 0
|
||||
}
|
||||
|
||||
// MARK: - UIPanGestureRecognizer Intermediation
|
||||
override func responds(to aSelector: Selector!) -> Bool {
|
||||
return super.responds(to: aSelector) || panGestureRecognizer.delegateProxy?.responds(to: aSelector) == true
|
||||
}
|
||||
|
||||
override func forwardingTarget(for aSelector: Selector!) -> Any? {
|
||||
if panGestureRecognizer.delegateProxy?.responds(to: aSelector) == true {
|
||||
return panGestureRecognizer.delegateProxy
|
||||
}
|
||||
return super.forwardingTarget(for: aSelector)
|
||||
return condition(offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// A gesture recognizer that looks for panning (dragging) gestures in a panel.
|
||||
public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
fileprivate weak var floatingPanel: Core?
|
||||
fileprivate var initialLocation: CGPoint = .zero
|
||||
private weak var floatingPanel: Core! // Core has this gesture recognizer as non-optional
|
||||
fileprivate func set(floatingPanel: Core) {
|
||||
self.floatingPanel = floatingPanel
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(target: nil, action: nil)
|
||||
if #available(iOS 11.0, *) {
|
||||
name = "FloatingPanelPanGestureRecognizer"
|
||||
}
|
||||
}
|
||||
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
initialLocation = touches.first?.location(in: view) ?? .zero
|
||||
if floatingPanel?.transitionAnimator != nil || floatingPanel?.moveAnimator != nil {
|
||||
if floatingPanel.transitionAnimator != nil || floatingPanel.moveAnimator != nil {
|
||||
self.state = .began
|
||||
}
|
||||
}
|
||||
|
||||
/// The delegate of the gesture recognizer.
|
||||
///
|
||||
/// - Note: The delegate is used by FloatingPanel itself. If you set your own delegate object, an
|
||||
@@ -1120,10 +1166,12 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
return super.delegate
|
||||
}
|
||||
set {
|
||||
guard newValue is Core else {
|
||||
let exception = NSException(name: .invalidArgumentException,
|
||||
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate. Use 'delegateProxy' property.",
|
||||
userInfo: nil)
|
||||
guard newValue is DelegateRouter else {
|
||||
let exception = NSException(
|
||||
name: .invalidArgumentException,
|
||||
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate. Use 'delegateProxy' property.",
|
||||
userInfo: nil
|
||||
)
|
||||
exception.raise()
|
||||
return
|
||||
}
|
||||
@@ -1131,12 +1179,44 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
}
|
||||
}
|
||||
|
||||
/// An object to intercept the delegate of the gesture recognizer.
|
||||
/// The default object implementing a set methods of the delegate of the gesture recognizer.
|
||||
///
|
||||
/// If an object adopting `UIGestureRecognizerDelegate` is set, the delegate methods are proxied to it.
|
||||
/// Use this property with ``delegateProxy`` when you need to use the default gesture behaviors in a proxy implementation.
|
||||
public var delegateOrigin: UIGestureRecognizerDelegate {
|
||||
return floatingPanel
|
||||
}
|
||||
|
||||
/// A proxy object to intercept the default behavior of the gesture recognizer.
|
||||
///
|
||||
/// `UIGestureRecognizerDelegate` methods implementing by this object are called instead of the default delegate,
|
||||
/// ``delegateOrigin``.
|
||||
public weak var delegateProxy: UIGestureRecognizerDelegate? {
|
||||
didSet {
|
||||
self.delegate = floatingPanel // Update the cached IMP
|
||||
self.delegate = floatingPanel?.panGestureDelegateRouter // Update the cached IMP
|
||||
}
|
||||
}
|
||||
|
||||
final class DelegateRouter: NSObject, UIGestureRecognizerDelegate {
|
||||
fileprivate unowned let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
|
||||
init(panGestureRecognizer: FloatingPanelPanGestureRecognizer) {
|
||||
self.panGestureRecognizer = panGestureRecognizer
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func responds(to aSelector: Selector!) -> Bool {
|
||||
return panGestureRecognizer.delegateProxy?.responds(to: aSelector) == true
|
||||
|| panGestureRecognizer.delegateOrigin.responds(to: aSelector)
|
||||
}
|
||||
|
||||
override func forwardingTarget(for aSelector: Selector!) -> Any? {
|
||||
if panGestureRecognizer.delegateProxy?.responds(to: aSelector) == true {
|
||||
return panGestureRecognizer.delegateProxy
|
||||
}
|
||||
if panGestureRecognizer.delegateOrigin.responds(to: aSelector) {
|
||||
return panGestureRecognizer.delegateOrigin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1207,7 +1287,7 @@ private class NumericSpringAnimator: NSObject {
|
||||
if isRunning {
|
||||
return false
|
||||
}
|
||||
log.debug("startAnimation --", displayLink)
|
||||
os_log(msg, log: devLog, type: .debug, "startAnimation -- \(displayLink)")
|
||||
isRunning = true
|
||||
displayLink.add(to: RunLoop.main, forMode: .common)
|
||||
return true
|
||||
@@ -1219,7 +1299,7 @@ private class NumericSpringAnimator: NSObject {
|
||||
if locked { lock.unlock() }
|
||||
}
|
||||
|
||||
log.debug("stopAnimation --", displayLink)
|
||||
os_log(msg, log: devLog, type: .debug, "stopAnimation -- \(displayLink)")
|
||||
isRunning = false
|
||||
displayLink.invalidate()
|
||||
if withoutFinishing {
|
||||
|
||||
@@ -10,7 +10,7 @@ extension CGFloat {
|
||||
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
}
|
||||
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
|
||||
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
return rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# ``FloatingPanel``
|
||||
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
Create a user interface to display the related content and utilities alongside the main content.
|
||||
|
||||
## Overview
|
||||
|
||||
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
|
||||
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in the Apple Maps, Shortcuts and Stocks app.
|
||||
The user interface displays related content and utilities alongside the main content.
|
||||
|
||||
|
||||
## Topics
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.6.1</string>
|
||||
<string>2.7.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An interface for generating layout information for a panel.
|
||||
@objc public protocol FloatingPanelLayout {
|
||||
@@ -265,12 +266,14 @@ class LayoutAdapter {
|
||||
}
|
||||
|
||||
var offsetFromMostExpandedAnchor: CGFloat {
|
||||
let offset: CGFloat
|
||||
switch position {
|
||||
case .top, .left:
|
||||
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
offset = edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
case .bottom, .right:
|
||||
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
offset = position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
}
|
||||
return offset.rounded(by: surfaceView.fp_displayScale)
|
||||
}
|
||||
|
||||
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
|
||||
@@ -710,7 +713,7 @@ class LayoutAdapter {
|
||||
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
defer {
|
||||
log.debug("update surface location = \(surfaceLocation)")
|
||||
os_log(msg, log: devLog, type: .debug, "update surface location = \(surfaceLocation)")
|
||||
}
|
||||
|
||||
let minConst: CGFloat = position(for: leastCoordinateState)
|
||||
@@ -750,9 +753,9 @@ class LayoutAdapter {
|
||||
defer {
|
||||
if forceLayout {
|
||||
layoutSurfaceIfNeeded()
|
||||
log.debug("activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
} else {
|
||||
log.debug("activateLayout for \(state)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,7 +790,7 @@ class LayoutAdapter {
|
||||
if let constraints = stateConstraints[state] {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
} else {
|
||||
log.error("Couldn't find any constraints for \(state)")
|
||||
os_log(msg, log: sysLog, type: .fault, "Error: can not find any constraints for \(state)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Must be a variable to use `hook` property in testing
|
||||
var log = {
|
||||
return Logger()
|
||||
}()
|
||||
|
||||
struct Logger {
|
||||
private let osLog: OSLog
|
||||
private let s = DispatchSemaphore(value: 1)
|
||||
|
||||
enum Level: Int, Comparable {
|
||||
case debug = 0
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "Debug:"
|
||||
case .info:
|
||||
return "Info:"
|
||||
case .warning:
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
typealias Hook = ((String, Level) -> Void)
|
||||
var hook: Hook?
|
||||
|
||||
fileprivate init() {
|
||||
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
|
||||
}
|
||||
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let _tag = tag.isEmpty ? "" : "\(tag):"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%{public}@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
if let filename = file.split(separator: "/").last {
|
||||
return filename + ":" + function
|
||||
} else {
|
||||
return file + ":" + function
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import os.log
|
||||
|
||||
let msg = StaticString("%{public}@")
|
||||
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
|
||||
#if FP_LOG
|
||||
let devLog = OSLog(subsystem: Logging.subsystem, category: "\(Logging.category):dev")
|
||||
#else
|
||||
let devLog = OSLog.disabled
|
||||
#endif
|
||||
|
||||
struct Logging {
|
||||
static let subsystem = "com.scenee.FloatingPanel"
|
||||
static let category = "FloatingPanel"
|
||||
private init() {}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An object for customizing the appearance of a surface view
|
||||
@objc(FloatingPanelSurfaceAppearance)
|
||||
@@ -318,7 +319,7 @@ public class SurfaceView: UIView {
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("surface view frame = \(frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "surface view frame = \(frame)")
|
||||
|
||||
containerView.backgroundColor = appearance.backgroundColor
|
||||
|
||||
|
||||
+28
-13
@@ -1,5 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import OSLog
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
@@ -7,22 +8,32 @@ class ControllerTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_warningRetainCycle() {
|
||||
let exp = expectation(description: "Warning retain cycle")
|
||||
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
|
||||
log.hook = {(log, level) in
|
||||
if log.contains("A memory leak will occur by a retain cycle because") {
|
||||
XCTAssert(level == .warning)
|
||||
exp.fulfill()
|
||||
}
|
||||
#if swift(>=5.5) // Avoid the 'No exact matches in call to initializer' build failure for OSLogStore when running this test case on iOS 13.7 using Xcode 12.5.1
|
||||
func test_warningRetainCycle() throws {
|
||||
guard #available(iOS 15.0, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 15 or later")
|
||||
}
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
myVC.loadViewIfNeeded()
|
||||
wait(for: [exp], timeout: 10)
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let found = try store
|
||||
.getEntries(
|
||||
at: store.position(timeIntervalSinceLatestBoot: 0),
|
||||
matching: .init(format: "subsystem == '\(Logging.subsystem)'")
|
||||
)
|
||||
.contains {
|
||||
$0.composedMessage.contains("A memory leak occurs due to a retain cycle, as")
|
||||
}
|
||||
XCTAssertTrue(found)
|
||||
}
|
||||
#endif
|
||||
|
||||
func test_addPanel() {
|
||||
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { fatalError() }
|
||||
let rootVC = UIViewController()
|
||||
rootVC.loadViewIfNeeded()
|
||||
rootVC.view.bounds = .init(origin: .zero, size: .init(width: 390, height: 844))
|
||||
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.addPanel(toParent: rootVC)
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .half).y)
|
||||
@@ -30,8 +41,13 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
func test_updateLayout_willTransition() {
|
||||
func test_updateLayout_willTransition() throws {
|
||||
guard #available(iOS 12, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 12 or later")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
throw XCTSkip("Unsupported iOS version: this test doesn't support iOS 17 or later")
|
||||
}
|
||||
class MyDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.userInterfaceStyle == .dark {
|
||||
@@ -314,7 +330,6 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
|
||||
fpc.move(to: .half, animated: false)
|
||||
print(1 / fpc.surfaceView.fp_displayScale)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
|
||||
+33
-33
@@ -7,7 +7,7 @@ class CoreTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_scrolllock() {
|
||||
func test_scrollLock() {
|
||||
let fpc = FloatingPanelController()
|
||||
|
||||
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
|
||||
@@ -292,7 +292,7 @@ class CoreTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func test_targetPosition_1positions() {
|
||||
func test_targetState_1positions() {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .full
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -309,7 +309,7 @@ class CoreTests: XCTestCase {
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
@@ -320,7 +320,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_2positions() {
|
||||
func test_targetState_2positions() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .half
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -340,7 +340,7 @@ class CoreTests: XCTestCase {
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -357,7 +357,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -375,7 +375,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_2positionsWithHidden() {
|
||||
func test_targetState_2positionsWithHidden() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -395,7 +395,7 @@ class CoreTests: XCTestCase {
|
||||
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -412,7 +412,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
fpc.move(to: .hidden, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -430,7 +430,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromFull() {
|
||||
func test_targetState_3positionsFromFull() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -442,7 +442,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -474,7 +474,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromFull_bottomEdge() {
|
||||
func test_targetState_3positionsFromFull_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -486,7 +486,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -518,7 +518,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf() {
|
||||
func test_targetState_3positionsFromHalf() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -530,7 +530,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -560,7 +560,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf_bottomEdge() {
|
||||
func test_targetState_3positionsFromHalf_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -572,7 +572,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -602,7 +602,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromTip() {
|
||||
func test_targetState_3positionsFromTip() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -615,7 +615,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -645,7 +645,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromTip_bottomEdge() {
|
||||
func test_targetState_3positionsFromTip_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -658,7 +658,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -688,7 +688,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsAllProjection() {
|
||||
func test_targetState_3positionsAllProjection() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -702,7 +702,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
@@ -715,7 +715,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .half
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
@@ -724,7 +724,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
@@ -736,7 +736,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsWithHidden() {
|
||||
func test_targetState_3positionsWithHidden() {
|
||||
class FloatingPanelLayout3PositionsWithHidden: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -754,11 +754,11 @@ class CoreTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.state, .hidden)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half),
|
||||
])
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
@@ -766,7 +766,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsWithHiddenWithoutFull() {
|
||||
func test_targetState_3positionsWithHiddenWithoutFull() {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -790,7 +790,7 @@ class CoreTests: XCTestCase {
|
||||
//let hiddenPos = fpc.surfaceLocation(for: .hidden)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
|
||||
@@ -806,7 +806,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
])
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
@@ -835,9 +835,9 @@ private class FloatingPanelLayout3PositionsBottomEdge: FloatingPanelTop2BottomTe
|
||||
}
|
||||
|
||||
private typealias TestParameter = (UInt, CGFloat, CGPoint, FloatingPanelState)
|
||||
private func assertTargetPosition(_ floatingPanel: Core, with params: [TestParameter]) {
|
||||
private func assertTargetState(_ floatingPanel: Core, with params: [TestParameter]) {
|
||||
params.forEach { (line, pos, velocity, result) in
|
||||
floatingPanel.surfaceView.frame.origin.y = pos
|
||||
XCTAssertEqual(floatingPanel.targetPosition(from: pos, with: velocity.y), result, line: line)
|
||||
XCTAssertEqual(floatingPanel.targetState(from: pos, with: velocity.y), result, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
final class GestureTests: XCTestCase {
|
||||
|
||||
func test_delegateProxy_shouldRecognizeSimultaneouslyWith() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRecognizeSimultaneouslyWith = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRecognizeSimultaneouslyWith += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
|
||||
// Check whether the default delegate method is called when the proxy delegate doesn't implement it.
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldRequireFailureOf: FloatingPanelPanGestureRecognizer()
|
||||
)
|
||||
)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldRequireFailureOf() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRequireFailureOf = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRequireFailureOf += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldBeRequiredToFailBy() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldBeRequiredToFailBy = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldBeRequiredToFailBy += 1
|
||||
return false
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldBeRequiredToFailBy: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 1)
|
||||
|
||||
// Check whether the delegate method of the "proxy" object is called.
|
||||
let otherPanGesture = UIPanGestureRecognizer()
|
||||
otherPanGesture.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertFalse(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
// Check whether the delegate method of the "default" object is called.
|
||||
let otherPanGesture2 = UIPanGestureRecognizer()
|
||||
otherPanGesture2.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture2
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
}
|
||||
}
|
||||
@@ -464,7 +464,6 @@ class LayoutTests: XCTestCase {
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
|
||||
@@ -73,7 +73,7 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
|
||||
}
|
||||
|
||||
class FloatingPanelProjectableBehavior: FloatingPanelBehavior {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let rootVC = UIViewController(nibName: nil, bundle: nil)
|
||||
rootVC.view.backgroundColor = .gray
|
||||
|
||||
let window = UIWindow()
|
||||
window.rootViewController = rootVC
|
||||
window.makeKeyAndVisible()
|
||||
self.window = window
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,43 +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>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Copyright © 2019 scenee. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
|
||||
<rect key="frame" x="0.0" y="626.5" width="375" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="TestingApp" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
|
||||
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
|
||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
|
||||
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="SfN-ll-jLj"/>
|
||||
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
|
||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
|
||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="x7j-FC-K8j"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
Reference in New Issue
Block a user