Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active June 5, 2024 15:44
Show Gist options
  • Select an option

  • Save robertmryan/f880d2ddfebbafee75601cf0a02fcb23 to your computer and use it in GitHub Desktop.

Select an option

Save robertmryan/f880d2ddfebbafee75601cf0a02fcb23 to your computer and use it in GitHub Desktop.
import UIKit
class ShadowedSwitch: UISwitch {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
func configure() {
backgroundColor = .systemBackground.withAlphaComponent(0.95)
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowRadius = 4
setContentCompressionResistancePriority(.required, for: .vertical)
setContentHuggingPriority(.required, for: .vertical)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2
}
}
class ViewController: UIViewController {
private lazy var floatingButton: UISwitch = {
let button = ShadowedSwitch()
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(openSheet(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
private func configureView() {
view.backgroundColor = .systemMint
view.addSubview(floatingButton)
let buttonBottomConstraint = floatingButton.bottomAnchor.constraint(
equalTo: view.layoutMarginsGuide.bottomAnchor,
constant: -10
)
buttonBottomConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
buttonBottomConstraint,
floatingButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
var childSheet: UISheetPresentationController?
@objc
private func openSheet(_ sender: Any) {
if let sheet = childSheet {
let current = sheet.selectedDetentIdentifier
sheet.animateChanges {
sheet.selectedDetentIdentifier = current == .hidden ? .medium : .hidden
}
return
}
let childViewController = ChildViewController()
if let sheet = childViewController.sheetPresentationController {
sheet.detents = [.hidden(), .small(), .medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.selectedDetentIdentifier = .medium
sheet.prefersGrabberVisible = true
sheet.delegate = self
childSheet = sheet
}
let constraint = childViewController.view.topAnchor.constraint(equalTo: floatingButton.bottomAnchor, constant: 10)
childViewController.isModalInPresentation = true
childViewController.animateAlongside = { [self] _ in
constraint.isActive = true
view.layoutIfNeeded()
}
present(childViewController, animated: true)
}
}
extension ViewController: UISheetPresentationControllerDelegate {
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
floatingButton.isOn = sheetPresentationController.selectedDetentIdentifier != .hidden
}
}
extension UISheetPresentationController.Detent.Identifier {
static var small: Self {
Self("small")
}
static var hidden: Self {
Self("hidden")
}
}
extension UISheetPresentationController.Detent {
static func small() -> UISheetPresentationController.Detent {
.custom(identifier: .small) { $0.maximumDetentValue * 0.15 }
}
static func hidden() -> UISheetPresentationController.Detent {
.custom(identifier: .hidden) { _ in 0 }
}
}
class ChildViewController: UIViewController {
var animateAlongside: ((any UIViewControllerTransitionCoordinatorContext) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
transitionCoordinator?.animate(alongsideTransition: { [self] context in
animateAlongside?(context)
animateAlongside = nil
})
}
}
@robertmryan

robertmryan commented Apr 26, 2024

Copy link
Copy Markdown
Author

Given that I changed the button to a UISwitch, the hardcoded corner radius is no longer prudent (and resulted in a weird artifact in the UI when the corner radius was set to 20 and the switch didn't happen to be 40pt tall). So I moved this corner rounding into a separate class, so it would adjust the rounding for the shadow dynamically.

This also has the virtue of abstracting the button layout code to a separate class and out of the view controller.

@robertmryan

Copy link
Copy Markdown
Author

I also changed the identifier names for hidden and small to follow the convention of medium and large. It’s not terribly relevant, but just an FYI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment