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
FloatingPanelLayout
is 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 paramters 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.
FloatingPanelPosition
is nowFloatingPanelState
.FloatingPanelPosition
in v2 is used to specify a panel position(top, left, bottom and right) in a screen.
FloatingPanelSurfaceView
isSurfaceView
only in Swift.FloatingPanelBackdropView
isBackdropView
only in Swift.FloatingPanelGrabberHandleView
isGrabberView
only 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.
layout
andbehavior
properties 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) {
surfaceOriginY
is nowsurfaceLocation
.updateLayout
is 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
targetState
argument type offloatingPanelWillEndDragging(_:withVelocity:targetState:)
is changed fromFloatingPanelState
toUnsafeMutablePointer<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.behavior
directly.
- Please update
floatingPanel(_:shouldRecognizeSimultaneouslyWith:)
- Please use
FloatingPanelController.panGestureRecognizer.delegateProxy
.
- Please use
position
property is added to determine a panel position.initialPosition
is nowinitialState
.supportedPositions
andinsetFor(position:)
are replaced withanchors
property.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.
FloatingPanelFullScreenLayout
is replaced with anchors using.superview
reference 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)
]
}
}
.topInteractionBuffer
and.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))
}
}
.springDecelerationRate
and.springResponseTime
properties are added to control the new spring effect of Numeric springing.
addAnimator(_:to:)
,removeAnimator(_:from:)
- They are moved into
floatingPanel(_:animatorForPresentingTo:)
andfloatingPanel(_:animatorForDismissingWith:)
ofFloatingPanelControllerDelegate
because 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
SurfaceAppearance
class andSurfaceView.appearance
property are added to specify the rounding corners, shadows and background color.SurfaceView.appearance
property avoidsAmbiguous use of 'cornerRadius'
error, for instance.SurfaceAppearance
enables 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.
grabberTopPadding
is nowgrabberHandlePadding
.topGrabberBarHeight
is nowgrabberAreaOffset
.grabberHandleWidth
andgrabberHandleHeight
are replaced withgrabberHandleSize
.
- The dismissal action of the backdrop is disabled by default.
- You can enable it to set
BackdropView.dismissalTapGestureRecognizer.isEnabled
totrue
.
- You can enable it to set
delegateProxy
property 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.