Last active
February 5, 2021 11:42
-
-
Save Edudjr/970adca5e47d10375f69765826e6b0c6 to your computer and use it in GitHub Desktop.
BottomSheet with variable size fitting collapsed view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: A UIKit based Playground for presenting user interface | |
// based on: https://stackoverflow.com/questions/37967555/how-can-i-mimic-the-bottom-sheet-from-the-maps-app | |
// This example includes an autoresizing collapsed-view. | |
// Clicking on "testing" will add another label, updating the collapsed-view's height. | |
// Click and drag the red section (BottomSheet) to update its position. | |
import UIKit | |
import PlaygroundSupport | |
let screenHeight: CGFloat = 400 | |
let screenWidth: CGFloat = 200 | |
class BottomSheet: UIViewController { | |
let topPadding: CGFloat = 50 | |
var bottomPadding: CGFloat { | |
screenHeight - collapsedView!.frame.height | |
} | |
private var collapsedView: UIView? | |
public var collapsedViewHeight: CGFloat { | |
collapsedView!.frame.height | |
} | |
public func addCollapsedView(_ innerView: UIView) { | |
collapsedView = innerView | |
innerView.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(innerView) | |
innerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true | |
innerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true | |
innerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | |
innerView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true | |
adjustCollapsedViewInitialHeight() | |
} | |
private func adjustCollapsedViewInitialHeight() { | |
view.layoutIfNeeded() | |
let bottomPadding = screenHeight - collapsedViewHeight | |
let bottomSheetHeight = view.frame.height | |
let bottomSheetWidth = view.frame.width | |
UIView.animate(withDuration: 0.2, delay: 0.0, options: [.allowUserInteraction], animations: { | |
self.view.frame = CGRect(x: 0, | |
y: bottomPadding, | |
width: bottomSheetWidth, | |
height: bottomSheetHeight) | |
}) | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
adjustCollapsedViewInitialHeight() | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
view.backgroundColor = .green | |
let gesture = UIPanGestureRecognizer(target: self, | |
action: #selector(Self.panGesture)) | |
view.addGestureRecognizer(gesture) | |
} | |
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) { | |
let translation = recognizer.translation(in: self.view) | |
let velocity = recognizer.velocity(in: self.view) | |
let y = self.view.frame.minY | |
if ( y + translation.y >= topPadding) && (y + translation.y <= bottomPadding ) { | |
self.view.frame = CGRect(x: 0, y: y + translation.y, width: view.frame.width, height: view.frame.height) | |
recognizer.setTranslation(CGPoint.zero, in: self.view) | |
} | |
if recognizer.state == .ended { | |
var duration = velocity.y < 0 ? Double((y - topPadding) / -velocity.y) : Double((bottomPadding - y) / velocity.y ) | |
duration = duration > 1.3 ? 1 : duration | |
UIView.animate(withDuration: duration, delay: 0.0, options: [.allowUserInteraction], animations: { | |
if velocity.y >= 0 { | |
self.view.frame = CGRect(x: 0, y: self.bottomPadding, width: self.view.frame.width, height: self.view.frame.height) | |
} else { | |
self.view.frame = CGRect(x: 0, y: self.topPadding, width: self.view.frame.width, height: self.view.frame.height) | |
} | |
}, completion: nil) | |
} | |
} | |
} | |
class ViewController: UIViewController { | |
var stack = UIStackView() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
view.backgroundColor = .gray | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
addBottomSheetView() | |
} | |
func addBottomSheetView() { | |
// 1- Init bottomSheetVC | |
let bottomSheetVC = BottomSheet() | |
bottomSheetVC.addCollapsedView(makeStackView()) | |
// 2- Add bottomSheetVC as a child view | |
self.addChild(bottomSheetVC) | |
self.view.addSubview(bottomSheetVC.view) | |
bottomSheetVC.didMove(toParent: self) | |
} | |
func makeStackView() -> UIStackView { | |
stack.backgroundColor = .red | |
stack.axis = .vertical | |
stack.addArrangedSubview(makeLabel()) | |
stack.addArrangedSubview(makeLabel()) | |
return stack | |
} | |
func makeLabel() -> UILabel { | |
let label = UILabel() | |
label.text = "testing" | |
label.isUserInteractionEnabled = true | |
let gesture = UITapGestureRecognizer(target: self, action: #selector(componentTapped)) | |
label.addGestureRecognizer(gesture) | |
return label | |
} | |
@objc func componentTapped() { | |
stack.addArrangedSubview(makeLabel()) | |
} | |
} | |
let vc = ViewController() | |
vc.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight) | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
PlaygroundPage.current.liveView = vc |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example:
