FloatingPanel 2.0 is the latest major release of FloatingPanel. As a major release, following Semantic Versioning conventions, 2.0 introduces API-breaking changes.
This guide is provided in order to ease the transition of existing applications using FloatingPanel 1.x to the latest APIs, as well as explain the design and structure of new and updated functionality.
- Swift 5.0
- iOS 11 (iOS 10 is still the deployment target, but not tested well)
- Xcode 11.0
- Top, left and right positioned panel
- FloatingPanel is not just a library for a bottom positioned panel, but also top, left and right positioned ones.
- Objective-C compatibility
- The entire APIs are exposed in Objective-C. So you can use them in Objective-C directly.
- Flexible and explicit layout customization
FloatingPanelLayoutis redesigned. There is no implicit rules to lay out a panel anymore.
- New spring animation without UIViewPropertyAnimator
- The new spring animation uses Numeric springing which is a very powerful tool for procedural animation. Therefore a library consumer is easy to modify a panel behavior by 2 parameters of the deceleration rate and response time.
- Handle the panel position anytime
floatingPanelDidMove(_:)delegate method is also called while a panel is moving. The method behavior becomes same asscrollViewDidScroll(_:)inUIScrollViewDelegate. And in the method a library consumer is able to change a panel location.
- Update the removal interaction's invocation
- Now you can invoke the removal interaction at any time where you want. There is no restrictions in the library.
- Fix many issues depending on API design
- See the following sections for details.
FloatingPanelPositionis nowFloatingPanelState.FloatingPanelPositionin v2 is used to specify a panel position(top, left, bottom and right) in a screen.
FloatingPanelSurfaceViewisSurfaceViewonly in Swift.FloatingPanelBackdropViewisBackdropViewonly in Swift.FloatingPanelGrabberHandleViewisGrabberViewonly in Swift.- "decelerate" term is replaced with "attract" because the panel's behavior is not unidirectional, but going back and forth so that it is settled to a location.
layoutandbehaviorproperties can be changed directly without using the delegate methods.
fpc.behavior = SearchPaneliPadBehavior()
fpc.layout = SearchPaneliPadLayout()
fpc.invalidateLayout() // If needed- The second argument of
addPanel(toParent:)changes to specify an index of subviews of a view in which a panel is added.
- public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) {
+ public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false) {surfaceOriginYis nowsurfaceLocation.updateLayoutis nowinvalidateLayout.- The scroll tracking API is changed a bit to support multiple scroll view tracking in the future.
- Now
untrack(scrollView:)is used to disable the scroll tracking.
- Now
floatingPanelDidEndDragging(_ vc:willAttract:)is added to check whether a panel will continue to move after dragging.floatingPanelDidMove(_:)behavior changes. The method is also called in the spring animation.- The removal interaction delegate is updated.
floatingPanel(_:shouldRemoveAt:with:)is added to determine whether it invokes the removal interaction in any state.floatingPanelWillRemove(_:)is added.
floatingPanel(_: FloatingPanelController, layoutFor size: CGSize)is added to respond to a layout change in regular size classes on iPad.
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout {
if aCondition(for: size) {
return SearchPanelLayout()
}
return SearchPanel2Layout()
}- The
targetStateargument type offloatingPanelWillEndDragging(_:withVelocity:targetState:)is changed fromFloatingPanelStatetoUnsafeMutablePointer<FloatingPanelState>to modify a target state on demand.
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
switch targetState.pointee {
case .full:
// do something...
case .half:
if aCondition {
targetState.pointee = .tip
}
default:
break
}
}floatingPanel(_:behaviorFor:)- Please update
FloatingPanelController.behaviordirectly.
- Please update
floatingPanel(_:shouldRecognizeSimultaneouslyWith:)- Please use
FloatingPanelController.panGestureRecognizer.delegateProxy.
- Please use
positionproperty is added to determine a panel position.initialPositionis nowinitialState.supportedPositionsandinsetFor(position:)are replaced withanchorsproperty.backdropAlphaFor(position:)is nowbackdropAlpha(for:).
class SearchPanelPadLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
...
]
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
}
...
}The following objects adopting FloatingPanelLayoutAnchoring protocol are added to configure the flexible and explicit layout.
This class is used to specify a panel layout using insets from a rectangle area of the superview or safe area.
FloatingPanelFullScreenLayoutis replaced with anchors using.superviewreference guide.FloatingPanelLayoutAnchor(fractionalInset:edge:referenceGuide:)lets you lay out a panel at a relative position in a reference rectangle area.
// Before:
class MyPanelLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .half: return 262.0
case .tip: return 44.0
case .hidden: return nil
}
}
}
// After:
class MyPanelLayout: FloatingPanelLayout {
var position: FloatingPanelPosition = .bottom
var initialState: FloatingPanelState { .half }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}This class is used to specify a panel layout using offsets from the intrinsic size layout.
- This replaces
FloatingPanelIntrinsicLayout. - This is also able to configure a fractional layout in the intrinsic size.
// Before:
class MyPanelIntrinsicLayout: FloatingPanelIntrinsicLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .half: return 262.0
case .tip: return 44.0
case .hidden: return nil
}
}
}
// After:
class MyPanelIntrinsicLayout: FloatingPanelLayout {
var position: FloatingPanelPosition = .bottom
var initialState: FloatingPanelState { .full }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 16.0, referenceGuide: .safeArea),
.half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}.topInteractionBufferand.bottomInteractionBuffer.- Please control the max/min range of the motion in
floatingPanelDidMove(_:)delegate method as below.
- Please control the max/min range of the motion in
func floatingPanelDidMove(_ fpc: FloatingPanelController) {
if fpc.isAttracting == false {
let loc = fpc.surfaceLocation
let minY = fpc.surfaceLocation(for: .full).y - 6.0
let maxY = fpc.surfaceLocation(for: .tip).y + 6.0
fpc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
}
}.springDecelerationRateand.springResponseTimeproperties are added to control the new spring effect of Numeric springing.
addAnimator(_:to:),removeAnimator(_:from:)- They are moved into
floatingPanel(_:animatorForPresentingTo:)andfloatingPanel(_:animatorForDismissingWith:)ofFloatingPanelControllerDelegatebecause they are used for view transitions.
- They are moved into
interactionAnimator(_:to:with:),moveAnimator(_:from:to:)- They are removed because the animators are replaced with the new spring effect.
removalVelocity,removalProgress- They are replaced with
floatingPanel(_:shouldRemoveAt:with:)ofFloatingPanelControllerDelegate
- They are replaced with
removalInteractionAnimator(_:with:)- It is integrated with
floatingPanel(_:animatorForDismissingWith:)ofFloatingPanelControllerDelegate.
- It is integrated with
SurfaceAppearanceclass andSurfaceView.appearanceproperty are added to specify the rounding corners, shadows and background color.SurfaceView.appearanceproperty avoidsAmbiguous use of 'cornerRadius'error, for instance.SurfaceAppearanceenables to apply layered box shadows into a surface to materialize it.
// Before:
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.shadowHidden = false
fpc.surfaceView.shadowColor = .black
fpc.surfaceView.shadowOffset = CGSize(width: 0, height: 16)
fpc.surfaceView.shadowRadius = 16.0
// After:
let appearance = SurfaceAppearance()
appearance.cornerRadius = 8.0
appearance.backgroundColor = .clear
let shadow = SurfaceAppearance.Shadow()
shadow.color = .black
shadow.offset = CGSize(width: 0, height: 16)
shadow.radius = 16
shadow.spread = 8
appearance.shadows = [shadow]
fpc.surfaceView.appearance = appearance- These properties are changed for the top, left and right positioned panel.
grabberTopPaddingis nowgrabberHandlePadding.topGrabberBarHeightis nowgrabberAreaOffset.grabberHandleWidthandgrabberHandleHeightare replaced withgrabberHandleSize.
- The dismissal action of the backdrop is disabled by default.
- You can enable it to set
BackdropView.dismissalTapGestureRecognizer.isEnabledtotrue.
- You can enable it to set
delegateProxyproperty is added to intercept the gesture recognizer delegate.
func layoutPanelForPad() {
fpc.behavior = SearchPaneliPadBehavior()
fpc.panGestureRecognizer.delegateProxy = self
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}UISpringTimingParameters(decelerationRate:frequencyResponse:initialVelocity:)initializer is added.- The directory structure and file names in the Xcode project changes.