Transmission
Transmission copied to clipboard
Present a sheet from UIKit
I'm trying out this lib and in my app, most of my navigation is still using UIKit because I'm still mixing UIKit and SwiftUI for now.
While the presentation view modifier works in many cases, it would be convenient in many cases to be able to present a sheet right from UIKit like this for instance.
present(viewController, transition: .sheet(.ideal))
Would it be something possible here? Thanks!
I recently made all of the presentation controllers open, so you can reuse the transitions. I haven't made a nice, easy to use API like you suggested but I can look into it.
In the meantime, you can use the .ideal detent like so:
import Transmission
let viewController = PresentationHostingController(
content: Color.blue.frame(height: 200)
)
if let sheetPresentationController = viewController.sheetPresentationController {
sheetPresentationController.detents = [
PresentationLinkTransition.SheetTransitionOptions.Detent.ideal.toUIKit(in: sheetPresentationController)
]
}
present(viewController, animated: true)
Thanks!
When using SwiftUI, is it needed to use PresentationHostingController? or should UIHostingController have the same behaviour here? Ideally I would like to avoid having to use a specific subclass or UIHostingController for my use case
It seems to work well unless I use a true UIKit UIViewController (not a SwiftUI view wrapped in UIHostingController)
Here without a UIScrollView, the grow animation looks weird, maybe just because of my UIStackView distribution though
https://github.com/user-attachments/assets/ab5b63cd-8212-4d49-8193-2d8422e22385
ViewController
class ScrollViewController: UIViewController {
private let scrollView: UIView = {
let scrollView = UIView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private let contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 20
stackView.distribution = .fill
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 20, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
return stackView
}()
// UI Elements
private let headerImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.backgroundColor = .systemGray5
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
imageView.image = UIImage(systemName: "photo")?.withRenderingMode(.alwaysTemplate)
imageView.tintColor = .systemGray3
return imageView
}()
private let titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 24, weight: .bold)
label.text = "Profile Information"
return label
}()
private let nameField: UITextField = {
let textField = UITextField()
textField.placeholder = "Enter your name"
textField.borderStyle = .roundedRect
textField.heightAnchor.constraint(equalToConstant: 44).isActive = true
return textField
}()
private let saveButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Save Profile", for: .normal)
button.backgroundColor = .systemBlue
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 8
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Profile"
setupScrollView()
setupStackView()
}
private func setupScrollView() {
view.addSubview(scrollView)
scrollView.addSubview(contentStackView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
])
}
private func setupStackView() {
// Add views to stack view in order
contentStackView.addArrangedSubview(headerImageView)
contentStackView.addArrangedSubview(titleLabel)
contentStackView.addArrangedSubview(nameField)
// Add some extra spacing before the save button
let spacerView = UIView()
spacerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
contentStackView.addArrangedSubview(spacerView)
contentStackView.addArrangedSubview(saveButton)
}
}
And when replacing the container UIView with a UIScrollView, it just fulling expand.
When using SwiftUI, is it needed to use PresentationHostingController? or should UIHostingController
If you don't use PresentationHostingController, then if your View height changes after its been presented, the UISheetPresentationController won't know and so it won't update the detent height.
It seems to work well unless I use a true UIKit UIViewController
The .ideal height is calculated based on the view's systemLayoutSizeFitting, which by default calculates based on autolayout constraints.
Here without a UIScrollView, the grow animation looks weird, maybe just because of my UIStackView distribution though
Yea, you'll probably need to account for stretching, with some kind of flexible spacer view or perhaps changing the alignment/distribution