Compare commits

..

41 Commits

Author SHA1 Message Date
Shin Yamamoto f683f987d8 Release v1.4.1 2019-04-03 23:14:56 +09:00
Shin Yamamoto 3626621e87 Fix the umbrella header name 2019-04-03 23:14:56 +09:00
Shin Yamamoto cc2d1eb002 Merge pull request #178 from SCENEE/notify-retain-cycle
Notify a retain cycle
2019-04-03 23:13:20 +09:00
Shin Yamamoto 5ba19bcf8b Add an attention comment in Samples app 2019-04-03 21:23:49 +09:00
Shin Yamamoto 8391686e28 Clean up Logger
`.fault` log is removed because the level are intended for capturing
system-level or multi-process errors only in `os_log`.
2019-04-03 21:23:49 +09:00
Shin Yamamoto 38327c917f Add tests to quick-check warnings 2019-04-03 21:23:49 +09:00
Shin Yamamoto fca0f399b2 Merge pull request #181 from SCENEE/fix-loop-crash
Fix an infinite loop crash in iOS 10
2019-04-03 21:17:39 +09:00
Shin Yamamoto e3b7ac0e99 Add 'Test' configuration 2019-04-03 08:21:35 +09:00
Shin Yamamoto 04cd357f68 Print retain cycle warnings 2019-04-03 08:21:35 +09:00
Shin Yamamoto 68f48f714d Fix an infinite loop crash in iOS 10 2019-04-02 21:47:56 +09:00
Shin Yamamoto fa586c494f Merge pull request #180 from SCENEE/fix-scroll-fitting
Fix scroll fitting
2019-04-02 09:43:37 +09:00
Shin Yamamoto b886a0da64 Fix scroll fitting
This is to prevent a surface frame jump on scroll fitting. In fact,
a scroll offset range for fitting should be -10..<0.
2019-03-30 18:52:48 +09:00
Shin Yamamoto 0616aec3d2 Prevent an unexpected layout update
The KVO for `safeAreaInset` can be invoked even when new and old values
are the same value so that the surface of a floating panel sometimes
jumps in dragging by an unexpected layout update called from a
`safeAreaInset` update.
2019-03-30 15:45:41 +09:00
Shin Yamamoto 7d6f295e72 Merge pull request #169 from SCENEE/fix-ambiguous-cornerradius
Open FloatingPanelSurfaceView.backgroundView
2019-03-29 21:05:25 +09:00
Shin Yamamoto 1c952b6dcb Open FloatingPanelSurfaceView.backgroundView
This change lets a user be able to set up a corner radius and shadow of
the surface view, and has the library not update them unnecessarily.

As a result, a user can escape "Ambiguous use of 'cornerRadius'" error
as below, if it's used with a `cornerRadius` property of `UIView`
extension defined by the user.

```swift
extension UIView {
    @objc dynamic var cornerRadius: CGFloat {
        get { return self.layer.cornerRadius }
        set(cornerRadius) {
            self.layer.masksToBounds = true
            self.layer.cornerRadius = cornerRadius
        }
    }
}

...

public extension FloatingPanelSurfaceView {
    @objc public dynamic var fp_cornerRadius: CGFloat {
        get { return backgroundView.layer.cornerRadius }
        set {
             if #available(iOS 11.0, *) {
                 contentView?.layer.masksToBounds = true
                 contentView?.layer.cornerRadius = newValue
                 contentView?.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
             }
             backgroundView.layer.masksToBounds = true
             backgroundView.layer.cornerRadius = newValue
       }
    }
}
```

squash! Open FloatingPanelSurfaceView.backgroundView
2019-03-29 20:34:11 +09:00
Shin Yamamoto 7e7c2a0fd7 Merge pull request #170 from SCENEE/fix-top-dragging-boundary
Fix top dragging boundary
2019-03-27 22:41:56 +09:00
Shin Yamamoto a15444d237 Modify the height calc with a top-most position
According to the previous change, `.half` position meaning is
also changed so that the height calc logic must be modified.

Previously, for example, `.half` position has 2 meaning. One is
a position at half of a screen. Another is a position when a
content is visible half. `.tip` position is also same.

Now `.half`/`.tip` position can display a full content if it's
top most. In the case, the surface height should fit to a height
for `.half`/`.tip` position.
2019-03-26 22:23:27 +09:00
Shin Yamamoto 5b100f3b22 Add FloatingPanelLayout.topMostState
The previous implementation has an implicit pre-condition where .full
position is supposed to be top most. It also means .full position should
be included in a set of the supported positions. But it's not
appropriate because there is a case when a user wants to configure a top
most position of a layout with a bottom inset. This commit lets a panel
work well even if .half/.tip position is top most.

According to the change, .full position means a position where a content
is not just visible fully, but also filled in a full screen almost.
2019-03-26 22:23:16 +09:00
Shin Yamamoto 9fa8a48c56 Fix top panning boundary 2019-03-26 11:41:13 +09:00
Shin Yamamoto cb54a2a7e1 Merge pull request #151 from SCENEE/remove-swapping-scroll-delegate
Remove swapping delegate
2019-03-26 09:55:47 +09:00
Shin Yamamoto ed02713ccc Merge pull request #168 from SCENEE/fix-safeareainsets
Change FloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
2019-03-25 09:50:46 +09:00
Shin Yamamoto 68e3fd2093 ChangeFloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
This solves the problem that a panel's layout and dragging can be
inappropriate. The root cause is that a value of the `safeAreaInsets`
is sometimes different from an actual value referenced from Auto Layout.
It's led by the design that the `safeAreaInsets` is defined as a stored
property. Therefore this commit resolves the issue.
2019-03-23 18:36:26 +09:00
Shin Yamamoto a43f73d7b1 Merge pull request #159 from SCENEE/fix-interruption
Fix interruption
2019-03-20 21:39:20 +09:00
Shin Yamamoto 61e0c4ed0a Merge pull request #165 from SCENEE/fix-ui-extensions
Fix UI extensions
2019-03-20 21:38:28 +09:00
Shin Yamamoto 53e0629b1d Merge pull request #162 from zntfdr/patch-2
Fix typo
2019-03-19 09:31:13 +09:00
Shin Yamamoto 2b1d6a3d8a Fix 'Redundant conformance' error of UIGestureRecognizerState 2019-03-18 10:21:36 +09:00
Federico Zanetello 5ba1fb3d95 Add missing space 2019-03-16 12:42:51 +07:00
Federico Zanetello 1b7c15cdb5 Fix typo 2019-03-16 12:38:54 +07:00
Shin Yamamoto 81fd85e993 Prevent bouncing a scroll content in a panel animating
This issue happens when a panel interacts from tip to half
in 'Show Tab Bar' -> 'Tab 3'.
2019-03-09 10:41:26 +09:00
Shin Yamamoto 8f4c08d5b3 Fix TabBarContentViewController for Tab 3 2019-03-09 10:36:31 +09:00
Shin Yamamoto 61b6429851 Fix the buggy top buffer in scroll tracking
Should not handle a panel gesture even when a zero scroll velocity.
If not, it causes that a scroll view will be handled after a panel
moves a bit in a top buffer. As a result, a panel frame jumps from
a location in a top buffer to topY.
2019-03-09 10:04:18 +09:00
Shin Yamamoto bbc6b39c08 Fix issues on an animation interruption
* Adjust a final position to work scroll tracking well
* Fix issues on an animation cancellation
2019-03-09 10:04:18 +09:00
Shin Yamamoto 3f812f4d6d Merge pull request #156 from SCENEE/fix-layout-assertion
Fix the layout assertion
2019-03-09 10:03:44 +09:00
Shin Yamamoto c9b15e4239 Merge pull request #158 from bryansum/master
Fix retain cycle in FloatingPanel
2019-03-09 09:57:11 +09:00
Bryan Summersett 5fbdb3d481 Make floatingPanel weak 2019-03-08 15:02:33 -08:00
Shin Yamamoto c59e1cd7fc Fix the layout assertion
The assertion didn't allow `.hidden` initial position.
2019-03-08 19:39:10 +09:00
Shin Yamamoto ce891e47da Merge pull request #155 from kfinteractive/master
Adds support for respecting initial safeAreaInsets.
2019-03-08 19:23:58 +09:00
Gunnar Herzog 7160e4a42e Removes unnecessary guard statement. 2019-03-07 08:56:29 +01:00
Gunnar Herzog eba857a285 Adds support for respecting initial safeAreaInsets. 2019-03-07 08:45:05 +01:00
Shin Yamamoto a1dd02c780 Stop scroll deceleration without swapping delegate 2019-03-05 08:31:27 +09:00
Shin Yamamoto 87ff5d629b Merge pull request #143 from SCENEE/release-v1.4.0
Release v1.4.0
2019-02-27 10:00:47 +09:00
14 changed files with 330 additions and 160 deletions
@@ -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
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.4.0"
s.version = "1.4.1"
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>
+53 -60
View File
@@ -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 fabs(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)
@@ -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()
}
@@ -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()
}
}
@@ -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 {
+22 -26
View File
@@ -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.
@@ -384,18 +397,12 @@ 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 {
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
if let scrollView = floatingPanel.scrollView,
let delegate = floatingPanel.userScrollViewDelegate,
vc.view.subviews.contains(scrollView) {
scrollView.delegate = delegate
}
}
if let vc = contentViewController {
@@ -428,25 +435,14 @@ 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, *) {
@@ -475,7 +471,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:
+20 -10
View File
@@ -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 {
@@ -376,12 +388,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 +416,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 +486,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.
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.0</string>
<string>1.4.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+14 -32
View File
@@ -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
+2 -1
View File
@@ -73,7 +73,7 @@ extension UIView {
}
}
#if __FP_LOG
extension UIGestureRecognizerState: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
@@ -86,6 +86,7 @@ extension UIGestureRecognizerState: CustomDebugStringConvertible {
}
}
}
#endif
extension UIScrollView {
var contentOffsetZero: CGPoint {
+47
View File
@@ -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
}
}
}
-14
View File
@@ -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() {}
}