A flexible UIKit component for creating draggable split-pane interfaces with smooth animations and customizable breakpoints. Perfect for building adaptive layouts with resizable content areas, like email clients, file browsers, or any master-detail interface.
- In Xcode, choose File ▶ Add Packages…
- Enter this repository URL:
https://github.com/jsmmth/SplitPaneKit.git - Click Add Package, select your target(s), and finish.
If you manage dependencies via a manifest:
// swift-tools-version:5.9
import PackageDescription
letpackage=Package(
name:"MyApp",
platforms:[.iOS(.v13)],
dependencies:[.package(url:"https://github.com/jsmmth/SplitPaneKit.git", from:"0.0.1"),],
targets:[.target(
name:"MyApp",
dependencies:[.product(name:"SplitPaneView",package:"SplitPaneView")])])Then in your code:
import SplitPaneKitThe main container that manages two content panes with a draggable divider between them. The bottom pane can be dragged to different predefined heights (breakpoints).
// Create with default configuration
letsplitPane=SplitPaneView()
// Or with custom configuration
letconfig=SplitPaneConfiguration(
cornerRadius:20,
handleColor:.systemGray3,
defaultBreakpoint:.half
)letsplitPane=SplitPaneView(configuration: config)Base class for content views that can be placed in either pane:
classMyContentView:SplitPaneContentView{overrideinit(frame:CGRect){
super.init(frame: frame)
// Setup your UI
}
// Optional: Provide a scroll view for dismissal handling
overridevardismissalHandlingScrollView:UIScrollView?{return myTableView
}}Define the possible positions for the split divider:
// Use predefined breakpoints
splitPane.setBreakpoints([.quarter, // 25% height
.half, // 50% height
.threeQuarters // 75% height
])
// Or create custom breakpoints
letcustomBreakpoint=SplitPaneBreakpoint(ratio:0.4, name:"custom")
splitPane.setBreakpoints([.quarter, customBreakpoint,.threeQuarters])classViewController:UIViewController{overridefunc viewDidLoad(){
super.viewDidLoad()
// Create split pane
letsplitPane=SplitPaneView()
view.addSubview(splitPane)
// Add constraints (full screen)
splitPane.translatesAutoresizingMaskIntoConstraints =falseNSLayoutConstraint.activate([
splitPane.topAnchor.constraint(equalTo: view.topAnchor),
splitPane.leadingAnchor.constraint(equalTo: view.leadingAnchor),
splitPane.trailingAnchor.constraint(equalTo: view.trailingAnchor),
splitPane.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
// Set content views
lettopContent=MyTopContentView()letbottomContent=MyBottomContentView()
splitPane.setTopPaneContent(topContent)
splitPane.setBottomPaneContent(bottomContent)
// Configure breakpoints
splitPane.setBreakpoints([.quarter,.half,.threeQuarters])}}// Transition to a specific breakpoint
splitPane.transitionTo(breakpoint:.half, animated:true)
// Get current position
let(progress, current, next)= splitPane.progressBetweenBreakpoints()print("Progress: \(progress) between \(current?.name ??"nil") and \(next?.name ??"nil")")Implement the delegate to respond to user interactions:
classViewController:UIViewController,SplitPaneViewDelegate{overridefunc viewDidLoad(){
super.viewDidLoad()
splitPane.delegate =self}func splitPaneView(_ splitPaneView:SplitPaneView, didTransitionTo breakpoint:SplitPaneBreakpoint){print("Transitioned to: \(breakpoint.name)")}func splitPaneView(_ splitPaneView:SplitPaneView, isDraggingWithTranslation translation:CGPoint, velocity:CGPoint){
// Respond to dragging if needed
}}Create views that respond to pane height changes for coordinated animations:
classAnimatedHeaderView:UIView,SplitPanePanEffectingView{func splitPaneDidUpdateHeight(_ splitPane:SplitPaneView, height:CGFloat, progress:CGFloat, currentBreakpoint:SplitPaneBreakpoint?, nextBreakpoint:SplitPaneBreakpoint?){
// Animate based on height changes
alpha = progress
transform =CGAffineTransform(scaleX:0.8+(0.2* progress), y:0.8+(0.2* progress))}func splitPaneDidTransitionToBreakpoint(_ splitPane:SplitPaneView, breakpoint:SplitPaneBreakpoint){
// Final state after transition
print("Settled at: \(breakpoint.name)")}}
// Register the view
splitPane.addPanEffectingView(animatedHeader)When using scroll views in the bottom pane, the split pane automatically handles the interaction between scrolling and dragging:
classBottomListView:SplitPaneContentView{privatelettableView=UITableView()overridevardismissalHandlingScrollView:UIScrollView?{return tableView // Enable drag-to-dismiss when at top of scroll
}}Customize every aspect via SplitPaneConfiguration:
| Property | Description | Default |
|---|---|---|
handleSize | Size of the drag handle | CGSize(width: 56, height: 4) |
handleColor | Color of the drag handle | .label.withAlphaComponent(0.08) |
handleSpacing | Space between panes | 20 |
cornerRadius | Corner radius of panes | 40 |
bottomPaneBackgroundColor | Background color of bottom pane | .secondarySystemGroupedBackground |
topPaneBackgroundColor | Background color of top pane | .secondarySystemGroupedBackground |
animationSettings | Spring animation configuration | .default |
topPaneShadow | Shadow configuration for top pane | .default |
bottomPaneShadow | Shadow configuration for bottom pane | .default |
dragThreshold | Minimum drag distance to trigger transition | 70 |
velocityThreshold | Minimum velocity to trigger transition | 300 |
defaultBreakpoint | Initial breakpoint | .quarter |
rubberBandingStrength | Rubber banding effect strength (0-1) | 0.7 |
hapticFeedbackStyle | Haptic feedback on transitions | .light |
Fine-tune animations with SplitPaneAnimationSettings:
letfastAnimation=SplitPaneAnimationSettings(
duration:0.25,
damping:0.9,
initialVelocity:0.8)varconfig=SplitPaneConfiguration.default
config.animationSettings = fastAnimationAdd depth with PaneShadowConfiguration:
letshadow=PaneShadowConfiguration(
color:.black,
opacity:0.15,
radius:12,
offset:CGSize(width:0, height:-2))varconfig=SplitPaneConfiguration.default
config.bottomPaneShadow = shadow
config.topPaneShadow =.none // No shadowCheck out the Examples folder for complete working demos including:
- Basic split pane setup
- Custom animations with pan effecting views

