Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a840df79e | |||
| 6851e3b072 | |||
| 5a2b079872 | |||
| f683f987d8 | |||
| 3626621e87 | |||
| cc2d1eb002 | |||
| 5ba19bcf8b | |||
| 8391686e28 | |||
| 38327c917f | |||
| fca0f399b2 | |||
| e3b7ac0e99 | |||
| 04cd357f68 | |||
| 68f48f714d | |||
| fa586c494f | |||
| b886a0da64 | |||
| 0616aec3d2 | |||
| 7d6f295e72 | |||
| 1c952b6dcb | |||
| 7e7c2a0fd7 | |||
| a15444d237 | |||
| 5b100f3b22 | |||
| 9fa8a48c56 | |||
| cb54a2a7e1 | |||
| ed02713ccc | |||
| 68e3fd2093 | |||
| a43f73d7b1 | |||
| 61e0c4ed0a | |||
| 53e0629b1d | |||
| 2b1d6a3d8a | |||
| 5ba1fb3d95 | |||
| 1b7c15cdb5 | |||
| 81fd85e993 | |||
| 8f4c08d5b3 | |||
| 61b6429851 | |||
| bbc6b39c08 | |||
| 3f812f4d6d | |||
| c9b15e4239 | |||
| 5fbdb3d481 | |||
| c59e1cd7fc | |||
| ce891e47da | |||
| 7160e4a42e | |||
| eba857a285 | |||
| a1dd02c780 | |||
| 87ff5d629b |
+12
-8
@@ -2,7 +2,6 @@ language: swift
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- next
|
||||
cache:
|
||||
directories:
|
||||
- /usr/local/Homebrew
|
||||
@@ -19,15 +18,20 @@ jobs:
|
||||
- stage: Build framework(swift 4.1)
|
||||
osx_image: xcode9.4
|
||||
script:
|
||||
- xcodebuild -scheme FloatingPanel clean build
|
||||
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.1 clean build
|
||||
|
||||
- stage: Build framework(swift 4.2)
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme FloatingPanel clean build
|
||||
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.2 clean build
|
||||
|
||||
- stage: Build framework(swift 5.0)
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.0 clean build
|
||||
|
||||
- stage: Carthage
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
before_install:
|
||||
- brew update
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
@@ -35,21 +39,21 @@ jobs:
|
||||
- carthage build --no-skip-current
|
||||
|
||||
- stage: Podspec
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- pod spec lint
|
||||
|
||||
- stage: Build maps example
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
|
||||
|
||||
- stage: Build stocks example
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
|
||||
|
||||
- stage: Build samples example
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -331,7 +331,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -499,7 +499,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -518,7 +518,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
/**
|
||||
- Attention: `FloatingPanelLayout` must not be applied by the parent view
|
||||
controller of a floating panel. But here `SampleListViewController` adopts it
|
||||
purposely to check if the library prints an appropriate warning.
|
||||
*/
|
||||
class SampleListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FloatingPanelControllerDelegate, FloatingPanelLayout {
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
@@ -828,6 +833,15 @@ class TabBarContentViewController: UIViewController {
|
||||
extension TabBarContentViewController: UITextViewDelegate {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
// Reset an invalid content offset by a user after updating the layout
|
||||
// of `consoleVC.textView`.
|
||||
// NOTE: FloatingPanel doesn't implicity reset the offset(i.e.
|
||||
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
|
||||
// infinit loop if a user also resets a content offset as below and,
|
||||
// in the situation, a user has to modify the library.
|
||||
if fpc.position != .full, fpc.surfaceView.frame.minY < fpc.originYOfSurface(for: .full) {
|
||||
scrollView.contentOffset = .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -331,7 +331,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "1.4.0"
|
||||
s.version = "1.5.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.
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
|
||||
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
|
||||
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
|
||||
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanelController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */; };
|
||||
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
|
||||
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */; };
|
||||
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */; };
|
||||
@@ -38,10 +38,10 @@
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanel.h; sourceTree = "<group>"; };
|
||||
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9CF2151169500CA77B8 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = "<group>"; };
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTests.swift; sourceTree = "<group>"; };
|
||||
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelController.swift; sourceTree = "<group>"; };
|
||||
@@ -94,7 +94,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanelController.h */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
|
||||
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
|
||||
@@ -113,7 +113,7 @@
|
||||
545DB9CE2151169500CA77B8 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9CF2151169500CA77B8 /* ViewTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */,
|
||||
545DB9D12151169500CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
@@ -126,7 +126,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */,
|
||||
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -246,7 +246,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */,
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -473,6 +473,116 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
54E79ADF224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E79AE0224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG __FP_LOG";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E79AE1224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -480,6 +590,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D32151169500CA77B8 /* Debug */,
|
||||
54E79ADF224F6C9800717BC6 /* Test */,
|
||||
545DB9D42151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -489,6 +600,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D62151169500CA77B8 /* Debug */,
|
||||
54E79AE0224F6C9800717BC6 /* Test */,
|
||||
545DB9D72151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -498,6 +610,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D92151169500CA77B8 /* Debug */,
|
||||
54E79AE1224F6C9800717BC6 /* Test */,
|
||||
545DB9DA2151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
||||
@@ -23,12 +23,31 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Test"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C92151169500CA77B8"
|
||||
BuildableName = "FloatingPanelTests.xctest"
|
||||
BlueprintName = "FloatingPanelTests"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C02151169500CA77B8"
|
||||
BuildableName = "FloatingPanel.framework"
|
||||
BlueprintName = "FloatingPanel"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
|
||||
@@ -25,7 +25,6 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
}
|
||||
}
|
||||
weak var userScrollViewDelegate: UIScrollViewDelegate?
|
||||
|
||||
private(set) var state: FloatingPanelPosition = .hidden {
|
||||
didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) }
|
||||
@@ -40,6 +39,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var animator: UIViewPropertyAnimator?
|
||||
|
||||
private var initialFrame: CGRect = .zero
|
||||
private var initialTranslationY: CGFloat = 0
|
||||
private var initialLocation: CGPoint = .nan
|
||||
@@ -91,7 +91,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
if to != .full {
|
||||
if state != layoutAdapter.topMostState {
|
||||
lockScrollView()
|
||||
}
|
||||
tearDownActiveInteraction()
|
||||
@@ -252,34 +252,25 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
if belowTop {
|
||||
// Scroll offset pinning
|
||||
switch state {
|
||||
case .full:
|
||||
if state == layoutAdapter.topMostState {
|
||||
if interactionInProgress {
|
||||
log.debug("settle offset --", initialScrollOffset.y)
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
} else {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
// Preserve the current content offset in moving from full.
|
||||
scrollView.contentOffset.y = initialScrollOffset.y
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
} else {
|
||||
if scrollView.contentOffset.y < 0 {
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
if offset < 0 {
|
||||
fitToBounds(scrollView: scrollView)
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
startInteraction(with: translation, at: location)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .half, .tip:
|
||||
guard scrollView.isDecelerating == false else {
|
||||
// Don't fix the scroll offset in animating the panel to half and tip.
|
||||
// It causes a buggy scrolling deceleration because `state` becomes
|
||||
// a target position in animating the panel on the interaction from full.
|
||||
return
|
||||
}
|
||||
// Fix the scroll offset in moving the panel from half and tip.
|
||||
scrollView.contentOffset.y = initialScrollOffset.y
|
||||
case .hidden:
|
||||
break
|
||||
} else {
|
||||
scrollView.setContentOffset(initialScrollOffset, animated: false)
|
||||
}
|
||||
|
||||
// Always hide a scroll indicator at the non-top.
|
||||
@@ -291,7 +282,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
if interactionInProgress {
|
||||
unlockScrollView()
|
||||
} else {
|
||||
if state == .full, scrollView.contentOffset.y < 0, velocity.y > 0 {
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
if state == layoutAdapter.topMostState, offset < 0, velocity.y > 0 {
|
||||
fitToBounds(scrollView: scrollView)
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
startInteraction(with: translation, at: location)
|
||||
@@ -306,11 +298,18 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
"translation = \(translation.y), location = \(location.y), velocity = \(velocity.y)")
|
||||
|
||||
if let animator = self.animator {
|
||||
log.debug("panel animation interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
animator.finishAnimation(at: .current)
|
||||
}
|
||||
|
||||
self.animator = nil
|
||||
|
||||
// A user can stop a panel at the nearest Y of a target position
|
||||
if abs(surfaceView.frame.minY - layoutAdapter.topY) < 1 {
|
||||
surfaceView.frame.origin.y = layoutAdapter.topY
|
||||
}
|
||||
}
|
||||
|
||||
if interactionInProgress == false,
|
||||
@@ -360,8 +359,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
guard
|
||||
state == .full, // When not .full, don't scroll.
|
||||
interactionInProgress == false // When interaction already in progress, don't scroll.
|
||||
state == layoutAdapter.topMostState, // When not top most(i.e. .full), don't scroll.
|
||||
interactionInProgress == false, // When interaction already in progress, don't scroll.
|
||||
surfaceView.frame.minY == layoutAdapter.topY
|
||||
else {
|
||||
return false
|
||||
}
|
||||
@@ -379,18 +379,15 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
|
||||
// 10 pt is introduced from my testing(there might be better one)
|
||||
// It should be low as possible because a user scroll view frame will
|
||||
// change as far as the specified value temporarily.
|
||||
// The zero offset is an exception because the offset is usually zero
|
||||
// when a panel moves from half or tip position to full.
|
||||
if offset > -10.0, offset != 0.0 {
|
||||
// The zero offset must be excluded because the offset is usually zero
|
||||
// after a panel moves from half/tip to full.
|
||||
if offset > 0.0 {
|
||||
return true
|
||||
}
|
||||
if scrollView.isDecelerating {
|
||||
return true
|
||||
}
|
||||
if velocity.y < 0 {
|
||||
if velocity.y <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -403,12 +400,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
// So here just preserve the current state if needed.
|
||||
log.debug("panningBegan -- location = \(location.y)")
|
||||
initialLocation = location
|
||||
switch state {
|
||||
case .full:
|
||||
if state == layoutAdapter.topMostState {
|
||||
if let scrollView = scrollView {
|
||||
initialScrollFrame = scrollView.frame
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
if let scrollView = scrollView {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
}
|
||||
@@ -492,6 +488,12 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
stopScrollDeceleration = (surfaceView.frame.minY > layoutAdapter.topY) // Projecting the dragging to the scroll dragging or not
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.stopScrollingWithDeceleration(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
|
||||
let targetPosition = self.targetPosition(with: velocity)
|
||||
let distance = self.distance(to: targetPosition)
|
||||
@@ -500,7 +502,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
if isRemovalInteractionEnabled, isBottomState {
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0,
|
||||
dy: min(fabs(velocity.y)/distance, behavior.removalVelocity)) : .zero
|
||||
dy: min(abs(velocity.y)/distance, behavior.removalVelocity)) : .zero
|
||||
|
||||
if shouldStartRemovalAnimation(with: velocityVector) {
|
||||
|
||||
@@ -518,7 +520,19 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
viewcontroller.delegate?.floatingPanelDidEndDragging(viewcontroller, withVelocity: velocity, targetPosition: targetPosition)
|
||||
|
||||
// Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating
|
||||
let isScrollEnabled = scrollView?.isScrollEnabled
|
||||
if let scrollView = scrollView, targetPosition != .full {
|
||||
scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
startAnimation(to: targetPosition, at: distance, with: velocity)
|
||||
|
||||
// Workaround: Reset `self.scrollView.isScrollEnabled`
|
||||
if let scrollView = scrollView, targetPosition != .full,
|
||||
let isScrollEnabled = isScrollEnabled {
|
||||
scrollView.isScrollEnabled = isScrollEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldStartRemovalAnimation(with velocityVector: CGVector) -> Bool {
|
||||
@@ -557,7 +571,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
guard interactionInProgress == false else { return }
|
||||
|
||||
initialFrame = surfaceView.frame
|
||||
if state == .full, let scrollView = scrollView {
|
||||
if state == layoutAdapter.topMostState, let scrollView = scrollView {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
} else {
|
||||
@@ -586,7 +600,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
interactionInProgress = false
|
||||
|
||||
// Prevent to keep a scroll view indicator visible at the half/tip position
|
||||
if targetPosition != .full {
|
||||
if state != layoutAdapter.topMostState {
|
||||
lockScrollView()
|
||||
}
|
||||
|
||||
@@ -605,7 +619,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
isDecelerating = true
|
||||
viewcontroller.delegate?.floatingPanelWillBeginDecelerating(viewcontroller)
|
||||
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(fabs(velocity.y)/distance, 30.0)) : .zero
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(abs(velocity.y)/distance, 30.0)) : .zero
|
||||
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
|
||||
animator.addAnimations { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
@@ -633,7 +647,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
stopScrollDeceleration = false
|
||||
// Don't unlock scroll view in animating view when presentation layer != model layer
|
||||
if targetPosition == .full {
|
||||
if state == layoutAdapter.topMostState {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
@@ -646,11 +660,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
switch targetPosition {
|
||||
case .full:
|
||||
return CGFloat(fabs(currentY - topY))
|
||||
return CGFloat(abs(currentY - topY))
|
||||
case .half:
|
||||
return CGFloat(fabs(currentY - middleY))
|
||||
return CGFloat(abs(currentY - middleY))
|
||||
case .tip:
|
||||
return CGFloat(fabs(currentY - bottomY))
|
||||
return CGFloat(abs(currentY - bottomY))
|
||||
case .hidden:
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
@@ -689,7 +703,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
// Distance travelled after decelerating to zero velocity at a constant rate.
|
||||
// Refer to the slides p176 of [Designing Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/)
|
||||
private func project(initialVelocity: CGFloat, decelerationRate: CGFloat = UIScrollViewDecelerationRateNormal) -> CGFloat {
|
||||
private func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat {
|
||||
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
|
||||
}
|
||||
|
||||
@@ -869,8 +883,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
private func settle(scrollView: UIScrollView) {
|
||||
log.debug("settle scroll view")
|
||||
|
||||
let frame = surfaceView.layer.presentation()?.frame ?? surfaceView.frame
|
||||
surfaceView.transform = .identity
|
||||
surfaceView.frame = frame
|
||||
scrollView.transform = .identity
|
||||
scrollView.frame = initialScrollFrame
|
||||
scrollView.contentOffset = scrollView.contentOffsetZero
|
||||
@@ -878,36 +893,14 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UIScrollViewDelegate Intermediation
|
||||
override func responds(to aSelector: Selector!) -> Bool {
|
||||
return super.responds(to: aSelector) || userScrollViewDelegate?.responds(to: aSelector) == true
|
||||
}
|
||||
|
||||
override func forwardingTarget(for aSelector: Selector!) -> Any? {
|
||||
if userScrollViewDelegate?.responds(to: aSelector) == true {
|
||||
return userScrollViewDelegate
|
||||
} else {
|
||||
return super.forwardingTarget(for: aSelector)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
if stopScrollDeceleration {
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
stopScrollDeceleration = false
|
||||
} else {
|
||||
let targetOffset = targetContentOffset.pointee
|
||||
userScrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
|
||||
// Stop scrolling on tip and half
|
||||
if state != .full, targetOffset == targetContentOffset.pointee {
|
||||
targetContentOffset.pointee.y = scrollView.contentOffset.y
|
||||
}
|
||||
}
|
||||
private func stopScrollingWithDeceleration(at contentOffset: CGPoint) {
|
||||
// Must use setContentOffset(_:animated) to force-stop deceleration
|
||||
scrollView?.setContentOffset(contentOffset, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
fileprivate var floatingPanel: FloatingPanel?
|
||||
fileprivate weak var floatingPanel: FloatingPanel?
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
if floatingPanel?.animator != nil {
|
||||
|
||||
@@ -72,14 +72,18 @@ public extension FloatingPanelBehavior {
|
||||
}
|
||||
|
||||
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat {
|
||||
#if swift(>=4.2)
|
||||
return UIScrollView.DecelerationRate.normal.rawValue
|
||||
#else
|
||||
return UIScrollViewDecelerationRateNormal
|
||||
#endif
|
||||
}
|
||||
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
public func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
return defaultBehavior.interactionAnimator(fpc, to: targetPosition, with: velocity)
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
private var _contentViewController: UIViewController?
|
||||
|
||||
private var floatingPanel: FloatingPanel!
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = FloatingPanelModalTransition()
|
||||
|
||||
@@ -200,7 +201,9 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
if #available(iOS 11.0, *) {}
|
||||
else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
if preSafeAreaInsets != layoutInsets {
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,13 +248,14 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
floatingPanel.layoutAdapter.safeAreaInsets != safeAreaInsets,
|
||||
preSafeAreaInsets != safeAreaInsets,
|
||||
self.floatingPanel.isDecelerating == false
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
floatingPanel.layoutAdapter.safeAreaInsets = safeAreaInsets
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
|
||||
setUpLayout()
|
||||
|
||||
@@ -267,6 +271,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
private func reloadLayout(for traitCollection: UITraitCollection) {
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.layoutAdapter.prepareLayout(in: self)
|
||||
|
||||
if let parent = self.parent {
|
||||
if let layout = layout 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.")
|
||||
}
|
||||
if let behavior = behavior 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setUpLayout() {
|
||||
@@ -294,9 +307,9 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
|
||||
guard let `self` = self else { return }
|
||||
self.update(safeAreaInsets: vc.layoutInsets)
|
||||
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (vc, change) in
|
||||
guard change.oldValue != change.newValue else { return }
|
||||
self?.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
@@ -338,7 +351,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
parent.view.addSubview(self.view)
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
parent.addChild(self)
|
||||
#else
|
||||
parent.addChildViewController(self)
|
||||
#endif
|
||||
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -351,7 +368,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.didMove(toParent: self)
|
||||
#else
|
||||
self.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,9 +388,20 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
hide(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.willMove(toParent: nil)
|
||||
#else
|
||||
self.willMove(toParentViewController: nil)
|
||||
#endif
|
||||
|
||||
self.view.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
self.removeFromParent()
|
||||
#else
|
||||
self.removeFromParentViewController()
|
||||
#endif
|
||||
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
@@ -384,25 +416,39 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
floatingPanel.move(to: to, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
/// Sets the view controller responsible for the content portion of the floating panel..
|
||||
/// Sets the view controller responsible for the content portion of the floating panel.
|
||||
public func set(contentViewController: UIViewController?) {
|
||||
if let vc = _contentViewController {
|
||||
#if swift(>=4.2)
|
||||
vc.willMove(toParent: nil)
|
||||
#else
|
||||
vc.willMove(toParentViewController: nil)
|
||||
vc.view.removeFromSuperview()
|
||||
vc.removeFromParentViewController()
|
||||
#endif
|
||||
|
||||
if let scrollView = floatingPanel.scrollView,
|
||||
let delegate = floatingPanel.userScrollViewDelegate,
|
||||
vc.view.subviews.contains(scrollView) {
|
||||
scrollView.delegate = delegate
|
||||
}
|
||||
vc.view.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.removeFromParent()
|
||||
#else
|
||||
vc.removeFromParentViewController()
|
||||
#endif
|
||||
}
|
||||
|
||||
if let vc = contentViewController {
|
||||
#if swift(>=4.2)
|
||||
addChild(vc)
|
||||
#else
|
||||
addChildViewController(vc)
|
||||
#endif
|
||||
|
||||
let surfaceView = floatingPanel.surfaceView
|
||||
surfaceView.add(contentView: vc.view)
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.didMove(toParent: self)
|
||||
#else
|
||||
vc.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
|
||||
_contentViewController = contentViewController
|
||||
@@ -428,33 +474,28 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scrollView: Specify a scroll view to continuously and seamlessly work in concert with interactions of the surface view or nil to cancel it.
|
||||
/// - Attention:
|
||||
/// The specified scroll view must be already assigned to the delegate property because the controller intermediates between the various delegate methods.
|
||||
public func track(scrollView: UIScrollView?) {
|
||||
if let trackingScrollView = floatingPanel.scrollView,
|
||||
let delegate = floatingPanel.userScrollViewDelegate {
|
||||
trackingScrollView.delegate = delegate // restore delegate
|
||||
floatingPanel.userScrollViewDelegate = nil
|
||||
}
|
||||
|
||||
guard let scrollView = scrollView else {
|
||||
floatingPanel.scrollView = nil
|
||||
return
|
||||
}
|
||||
|
||||
floatingPanel.scrollView = scrollView
|
||||
if scrollView.delegate !== floatingPanel {
|
||||
floatingPanel.userScrollViewDelegate = scrollView.delegate
|
||||
scrollView.delegate = floatingPanel
|
||||
}
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
#if swift(>=4.2)
|
||||
children.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#else
|
||||
childViewControllers.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -475,7 +516,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
setUpLayout()
|
||||
}
|
||||
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view.
|
||||
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
|
||||
switch pos {
|
||||
case .full:
|
||||
@@ -508,10 +549,10 @@ extension FloatingPanelController {
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
@objc public func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
|
||||
}
|
||||
@objc public func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
if let fpc = parent as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
|
||||
@@ -142,7 +142,9 @@ class FloatingPanelLayoutAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
var safeAreaInsets: UIEdgeInsets = .zero
|
||||
private var safeAreaInsets: UIEdgeInsets {
|
||||
return vc?.layoutInsets ?? .zero
|
||||
}
|
||||
|
||||
private var initialConst: CGFloat = 0.0
|
||||
|
||||
@@ -178,6 +180,16 @@ class FloatingPanelLayoutAdapter {
|
||||
return supportedPositions
|
||||
}
|
||||
|
||||
var topMostState: FloatingPanelPosition {
|
||||
if supportedPositions.contains(.full) {
|
||||
return .full
|
||||
}
|
||||
if supportedPositions.contains(.half) {
|
||||
return .half
|
||||
}
|
||||
return .tip
|
||||
}
|
||||
|
||||
var topY: CGFloat {
|
||||
if supportedPositions.contains(.full) {
|
||||
switch layout {
|
||||
@@ -258,7 +270,11 @@ class FloatingPanelLayoutAdapter {
|
||||
}
|
||||
|
||||
func updateIntrinsicHeight() {
|
||||
#if swift(>=4.2)
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
#else
|
||||
let fittingSize = UILayoutFittingCompressedSize
|
||||
#endif
|
||||
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
|
||||
var safeAreaBottom: CGFloat = 0.0
|
||||
if #available(iOS 11.0, *) {
|
||||
@@ -376,12 +392,10 @@ class FloatingPanelLayoutAdapter {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
updateIntrinsicHeight()
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom)
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: -fullInset)
|
||||
default:
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: -(safeAreaInsets.top + fullInset))
|
||||
let const = -(positionY(for: topMostState))
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: const)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([heightConstraint])
|
||||
@@ -406,10 +420,10 @@ class FloatingPanelLayoutAdapter {
|
||||
let minY: CGFloat = {
|
||||
var ret: CGFloat = 0.0
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
|
||||
ret = topY
|
||||
default:
|
||||
ret = fullInset
|
||||
ret = topY - safeAreaInsets.top
|
||||
}
|
||||
if allowsTopBuffer {
|
||||
ret -= layout.topInteractionBuffer
|
||||
@@ -476,8 +490,8 @@ class FloatingPanelLayoutAdapter {
|
||||
private func checkLayoutConsistance() {
|
||||
// Verify layout configurations
|
||||
assert(supportedPositions.count > 0)
|
||||
assert(supportedPositions.contains(layout.initialPosition),
|
||||
"Does not include an initial potision(\(layout.initialPosition)) in supportedPositions(\(supportedPositions))")
|
||||
assert(supportedPositions.union([.hidden]).contains(layout.initialPosition),
|
||||
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
|
||||
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
|
||||
|
||||
@@ -59,7 +59,7 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
/// The color of the surface border.
|
||||
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
|
||||
|
||||
private var backgroundView: UIView!
|
||||
public var backgroundView: UIView!
|
||||
private var backgroundHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
private struct Default {
|
||||
@@ -126,8 +126,11 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
private func updateLayers() {
|
||||
backgroundView.backgroundColor = color
|
||||
backgroundView.layer.masksToBounds = true
|
||||
backgroundView.layer.cornerRadius = cornerRadius
|
||||
|
||||
if cornerRadius != 0.0, backgroundView.layer.cornerRadius != cornerRadius {
|
||||
backgroundView.layer.masksToBounds = true
|
||||
backgroundView.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
if shadowHidden == false {
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
@@ -138,13 +141,19 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
}
|
||||
|
||||
private func updateContentViewMask() {
|
||||
guard
|
||||
let contentView = contentView,
|
||||
cornerRadius != 0.0,
|
||||
contentView.layer.cornerRadius != cornerRadius
|
||||
else { return }
|
||||
|
||||
if #available(iOS 11, *) {
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
contentView?.layer.masksToBounds = true
|
||||
contentView?.layer.cornerRadius = cornerRadius
|
||||
contentView?.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
contentView.layer.masksToBounds = true
|
||||
contentView.layer.cornerRadius = cornerRadius
|
||||
contentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
} else {
|
||||
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user can mask the content view manually in an application.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.0</string>
|
||||
<string>1.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -10,7 +10,6 @@ var log = {
|
||||
return Logger()
|
||||
}()
|
||||
|
||||
#if __FP_LOG
|
||||
struct Logger {
|
||||
private let osLog: OSLog
|
||||
private let s = DispatchSemaphore(value: 1)
|
||||
@@ -20,29 +19,17 @@ struct Logger {
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
case fault = 4
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .debug: return "DEBUG"
|
||||
case .info: return "INFO"
|
||||
case .warning: return "WARNING"
|
||||
case .error: return "ERROR"
|
||||
case .fault: return "FAULT"
|
||||
}
|
||||
}
|
||||
var shortName: String {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "D/"
|
||||
case .info:
|
||||
return "I/"
|
||||
case .warning:
|
||||
return "W/"
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "E/"
|
||||
case .fault:
|
||||
return "F/"
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
@@ -50,9 +37,8 @@ struct Logger {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
case .fault: return .fault
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +56,14 @@ struct Logger {
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let log = "\(level.shortName) \(message) \(extraMessage) (\(function):\(line))"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
os_log("%@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
@@ -84,7 +77,9 @@ struct Logger {
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
@@ -98,17 +93,4 @@ struct Logger {
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func fault(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.fault, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
#else
|
||||
struct Logger {
|
||||
func debug(_ log: Any, _ arguments: Any...) { }
|
||||
func info(_ log: Any, _ arguments: Any...) { }
|
||||
func warning(_ log: Any, _ arguments: Any...) { }
|
||||
func error(_ log: Any, _ arguments: Any...) { }
|
||||
func fault(_ log: Any, _ arguments: Any...) { }
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -73,7 +73,21 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if __FP_LOG
|
||||
#if swift(>=4.2)
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "began"
|
||||
case .changed: return "changed"
|
||||
case .failed: return "failed"
|
||||
case .cancelled: return "cancelled"
|
||||
case .ended: return "endeded"
|
||||
case .possible: return "possible"
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension UIGestureRecognizerState: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
@@ -86,6 +100,8 @@ extension UIGestureRecognizerState: CustomDebugStringConvertible {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extension UIScrollView {
|
||||
var contentOffsetZero: CGPoint {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ViewTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
|
||||
func test_WarningRetainCycle() {
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
myVC.loadViewIfNeeded()
|
||||
// Check if there are memory leak warnings in console logs
|
||||
}
|
||||
}
|
||||
|
||||
class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
|
||||
var fpc: FloatingPanelController?
|
||||
override func viewDidLoad() {
|
||||
fpc = FloatingPanelController(delegate: self)
|
||||
fpc?.addPanel(toParent: self)
|
||||
}
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return self
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
|
||||
return self
|
||||
}
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 69.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanelController
|
||||
|
||||
class ViewTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
|
||||
# FloatingPanel
|
||||
|
||||
|
||||
Reference in New Issue
Block a user