๐ฅ Apple-Inspired Puller
Apple has released a sheet presentation controller in iOS 15, which works great. In iOS 16, developers can customize the detents of the sheet. However, we would like to use these features in iOS 14 as well. That's why I have developed a puller presentation controller with the same API as Apple's. The puller works even in iOS 11. Please feel free to use the puller and give it a star if you find it useful.
๐ Features
- Extremely easy to use with a native API-like interface.
- Customizable puller presentation animation with two presets: default and spring.
- Compatible with iOS 11+, the puller offers four preset detents:
medium
,large
,full
andfitsContent
. In thefull
state, the puller supports device corners, like the Apple Music app. When using thefitsContent
state, the puller adapts its height to the default height of the presenting view controller's view. Additionally, you can set as manycustom
detents you want. - Supports inside and outside drag indicators.
- Supports interactive pop gesture.
- Works seamlessly with ScrollView.
- Compatible with keyboard and device rotation.
- Can be opened in a similar style to the AirPods Pro sheet as shown by Apple.
- Includes various settings for customization and control.
- Unfortunately, the puller doesnโt support view controllers like
UIColorPickerViewController
cause they are running in another process and usingUIRemoteView
under the hood.
๐ก Attention
The puller uses a specific approach to obtain the screen corner radius by adding a displayCornerRadius
property to UIScreen
, which reads the private _displayCornerRadius
. The selector somewhat obscured, which usually means it will get past app review. However, use at your own risk!
๐ Examples
Default puller
Default puller with three detents: custom, medium and large. Drag indicator is inside.
Source Code
let viewController = UIViewController()
let pullerModel = PullerModel(detents: [.custom(0.25), .medium, .full],
dragIndicator: .inside(.black))
presentAsPuller(viewController, model: pullerModel)
Spring puller
Spring puller in full state. Drag indicator is outside.
Source Code
let viewController = UIViewController()
let pullerModel = PullerModel(animator: .spring,
detents: [.full],
dragIndicator: .outside(.black))
presentAsPuller(viewController, model: pullerModel)
Supporting interactive pop gesture
Spring puller in full state that supports interactive pop gesture.
Source Code
let viewController = UIViewController()
let pullerModel = PullerModel(animator: .spring,
detents: [.full],
supportsInteractivePopGesture: true) // it's true by default
presentAsPuller(viewController, model: pullerModel)
Dialog style
Dialog style puller resembles Apple's AirPods Pro sheet.
Source Code
let viewController = UIViewController()
let pullerModel = PullerModel(animator: .spring,
detents: [.medium],
hasDynamicHeight: false)
presentAsPuller(viewController, model: pullerModel)
fitsContent
detent
How to use The intrinsicContentSize
property of the view controller being presented must be set for the fitsContent
detent to work properly in the puller. If the intrinsicContentSize
property is not set, the puller will default to the large
detent.
Source Code
class YourViewController: UIViewController {
override func loadView() {
view = ResizableView()
}
override func viewDidLoad() {
super.viewDidLoad()
(view as? ResizableView)?.defaultHeight = 300
}
}
class ResizableView: UIView {
var defaultHeight: CGFloat?
override var intrinsicContentSize: CGSize {
if let defaultHeight = defaultHeight {
return CGSize(width: UIView.noIntrinsicMetric, height: defaultHeight)
}
return super.intrinsicContentSize
}
}
let viewController = YourViewController()
let pullerModel = PullerModel(animator: .spring,
detents: [.fitsContent])
presentAsPuller(viewController, model: pullerModel)
Settings in Demo
Demo app includes settings for adjusting animation speed and customizing puller behavior.