[英]iOS 15: using UISheetPresentationController in SwiftUI
I'm really struggling to wrap the new iOS 15 UISheetPresentationController
for use in SwiftUI (for a half-modal).我真的很难包装新的 iOS 15
UISheetPresentationController
以用于 SwiftUI(用于半模态)。 I understand that I should inherit UIViewControllerRepresentable
.我知道我应该继承
UIViewControllerRepresentable
。 Based upon an example I have for a custom ImagePicker, I've not been able to make this work.根据我对自定义 ImagePicker 的示例,我无法完成这项工作。
Can anyone help?任何人都可以帮忙吗? In particular I don't know to get a handle on the
presentedViewController
needed to init the UISheetPresentationController
itself:特别是,我不知道要获得初始化
UISheetPresentationController
本身所需的presentedViewController
的句柄:
func makeUIViewController(context: UIViewControllerRepresentableContext<KitSheet>) -> UISheetPresentationController {
let sheet = UISheetPresentationController(presentedViewController: <#T##UIViewController#>, presenting: <#T##UIViewController?#>)
sheet.delegate = context.coordinator
return sheet
}
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller
If you want the Image Picker如果你想要图像选择器
import SwiftUI
///This is the ParentView
@available(iOS 15.0, *)
struct CustomSheet: View {
//To control the picker
@State var presentImagePicker: Bool = false
//To house the selected image
@State var uiImage: UIImage = UIImage.remove
var body: some View {
VStack{
Button(presentImagePicker ? "dismiss image picker" : "present image picker", action: {
presentImagePicker.toggle()
})
Image(uiImage: uiImage).resizable().frame(width: 100, height: 100, alignment: .center)
CustomImageSheet_UI(presentImagePicker: $presentImagePicker, uiImage: $uiImage)
}
}
}
@available(iOS 15.0, *)
struct CustomImageSheet_UI: UIViewControllerRepresentable {
@Binding var presentImagePicker: Bool
@Binding var uiImage: UIImage
init(presentImagePicker: Binding<Bool>,uiImage: Binding<UIImage>) {
self._presentImagePicker = presentImagePicker
self._uiImage = uiImage
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> CustomImageSheetViewController {
let vc = CustomImageSheetViewController(coordinator: context.coordinator)
return vc
}
func updateUIViewController(_ uiViewController: CustomImageSheetViewController, context: Context) {
if presentImagePicker{
uiViewController.presentImagePicker()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: CustomImageSheet_UI
init(_ parent: CustomImageSheet_UI) {
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
if parent.presentImagePicker{
parent.presentImagePicker = false
}
}
//Adjust the variable when the user cancels
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
if parent.presentImagePicker{
parent.presentImagePicker = false
}
}
//Get access to the selected image
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
parent.uiImage = image
parent.presentImagePicker = false
}
}
}
}
//This custom view controller
@available(iOS 15.0, *)
class CustomImageSheetViewController: UIViewController {
let coordinator: CustomImageSheet_UI.Coordinator
init(coordinator: CustomImageSheet_UI.Coordinator) {
self.coordinator = coordinator
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
dismiss(animated: true, completion: nil)
}
//This is mostly code from the Apple sample
//https://developer.apple.com/documentation/uikit/uiviewcontroller/customize_and_resize_sheets_in_uikit
func presentImagePicker(){
guard presentedViewController == nil else {
dismiss(animated: true, completion: {
self.presentImagePicker()
})
return
}
let imagePicker = UIImagePickerController()
imagePicker.delegate = coordinator
imagePicker.modalPresentationStyle = .popover
//Added the presentation controller delegate to detect if the user swipes to dismiss
imagePicker.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate
if let popover = imagePicker.popoverPresentationController {
popover.sourceView = super.view
let sheet = popover.adaptiveSheetPresentationController
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier =
.medium
sheet.prefersScrollingExpandsWhenScrolledToEdge =
false
sheet.prefersEdgeAttachedInCompactHeight =
true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(imagePicker, animated: true, completion: nil)
}
}
If you want one that takes any SwiftUI View
it only needs a few changes.如果您想要一个采用任何 SwiftUI
View
它只需要一些更改。
@available(iOS 15.0, *)
struct CustomSheet: View {
@State private var isPresented = false
var body: some View {
VStack{
Button(isPresented ? "dismiss content" : "present content", action: {
isPresented.toggle()
})
CustomSheet_UI(isPresented: $isPresented, detents: [.medium()], largestUndimmedDetentIdentifier: .large){
Rectangle()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.foregroundColor(.clear)
.border(Color.blue, width: 3)
.overlay(Text("Hello, World!"))
}
}
}
}
@available(iOS 15.0, *)
struct CustomSheet_UI<Content: View>: UIViewControllerRepresentable {
let content: Content
@Binding var isPresented: Bool
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
init(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> Content) {
self.content = content()
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
self._isPresented = isPresented
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> CustomSheetViewController<Content> {
let vc = CustomSheetViewController(coordinator: context.coordinator, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, content: {content})
return vc
}
func updateUIViewController(_ uiViewController: CustomSheetViewController<Content>, context: Context) {
if isPresented{
uiViewController.presentModalView()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
var parent: CustomSheet_UI
init(_ parent: CustomSheet_UI) {
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
if parent.isPresented{
parent.isPresented = false
}
}
}
}
@available(iOS 15.0, *)
class CustomSheetViewController<Content: View>: UIViewController {
let content: Content
let coordinator: CustomSheet_UI<Content>.Coordinator
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
init(coordinator: CustomSheet_UI<Content>.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> Content) {
self.content = content()
self.coordinator = coordinator
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
dismiss(animated: true, completion: nil)
}
func presentModalView(){
let hostingController = UIHostingController(rootView: content)
hostingController.modalPresentationStyle = .popover
hostingController.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate
if let hostPopover = hostingController.popoverPresentationController {
hostPopover.sourceView = super.view
let sheet = hostPopover.adaptiveSheetPresentationController
sheet.detents = detents
sheet.largestUndimmedDetentIdentifier =
largestUndimmedDetentIdentifier
sheet.prefersScrollingExpandsWhenScrolledToEdge =
prefersScrollingExpandsWhenScrolledToEdge
sheet.prefersEdgeAttachedInCompactHeight =
prefersEdgeAttachedInCompactHeight
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
if presentedViewController == nil{
present(hostingController, animated: true, completion: nil)
}
}
}
The way this API seems to work is to use a regular UIViewController
and in viewDidLoad
you can grab the UISheetPresentationController
and configure it.这个 API 的工作方式似乎是使用常规的
UIViewController
并在viewDidLoad
中您可以获取UISheetPresentationController
并对其进行配置。 By default all iOS 13+ modals are sheets automatically.默认情况下,所有 iOS 13+ 模态都是自动工作表。
class SheetContentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let sheetPresentationController = presentationController as? UISheetPresentationController {
sheetPresentationController.detents = [.medium(), .large()]
sheetPresentationController.prefersGrabberVisible = true
}
}
What I am currently doing is using a UIHostingController that acts as the sheet.我目前正在做的是使用充当工作表的 UIHostingController。
Create a custom hosting controller class.创建自定义托管 controller class。
import UIKit
import SwiftUI
@available(iOS 15.0, *)
final class SheetHostingController<T: View>: UIHostingController<T>, UISheetPresentationControllerDelegate {
// MARK: - Properties
private let detents: [UISheetPresentationController.Detent]
private let prefersEdgeAttachedInCompactHeight: Bool
private let prefersScrollingExpandsWhenScrolledToEdge: Bool
// MARK: - Initialization
init(
rootView: T,
title: String? = nil,
largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode = .never,
detents: [UISheetPresentationController.Detent] = [.medium(), .large()],
prefersEdgeAttachedInCompactHeight: Bool = true,
prefersScrollingExpandsWhenScrolledToEdge: Bool = true
) {
self.detents = detents
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
super.init(rootView: rootView)
navigationItem.title = title
navigationItem.largeTitleDisplayMode = largeTitleDisplayMode
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
if let sheetPresentationController = presentationController as? UISheetPresentationController {
sheetPresentationController.delegate = self
sheetPresentationController.detents = detents
sheetPresentationController.prefersGrabberVisible = true
sheetPresentationController.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
sheetPresentationController.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
}
}
// MARK: - Public Methods
func set(to detentIdentifier: UISheetPresentationController.Detent.Identifier?) {
guard let sheetPresentationController = presentationController as? UISheetPresentationController else { return }
sheetPresentationController.animateChanges {
sheetPresentationController.selectedDetentIdentifier = detentIdentifier
}
}
// MARK: - UISheetPresentationControllerDelegate
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
// Currently not working?
}
}
and than you can present it in your app flow而且你可以在你的应用流程中展示它
let swiftUIView = SomeSwiftUIView()
let sheetHostingController = SheetHostingController(rootView: swiftUIView)
someViewController.present(sheetHostingController, animated: true)
Currently the delegate is not firing for me to detect if the sheet was changed non-programatically eg drag gesture.目前,委托并未让我检测工作表是否以非编程方式更改,例如拖动手势。 Not sure this is an early beta bug.
不确定这是早期的 beta 错误。 Also a bit of a shame they have not added a small detent setting, made the sheet non dismissible and the view behind interactive like in maps.
也有点遗憾的是,他们没有添加一个小的定位设置,使工作表不可关闭,并且像地图中那样交互背后的视图。
Found the options given here a wee bit complex, so here is an alternative on 3 steps:发现这里给出的选项有点复杂,所以这里有 3 个步骤的替代方案:
Subclass UIHostingController
and personalise子类
UIHostingController
和个性化
class HalfSheetController<Content>: UIHostingController<Content> where Content : View {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let presentation = sheetPresentationController {
// configure at will
presentation.detents = [.medium()]
}
}
}
Create an UIViewControllerRepresentable
using your UIHostingController
, we are using a ViewBuilder here for maximum flexibility.使用您的
UIHostingController
创建一个UIViewControllerRepresentable
,我们在这里使用 ViewBuilder 以获得最大的灵活性。
struct HalfSheet<Content>: UIViewControllerRepresentable where Content : View {
private let content: Content
@inlinable init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIViewController(context: Context) -> HalfSheetController<Content> {
return HalfSheetController(rootView: content)
}
func updateUIViewController(_: HalfSheetController<Content>, context: Context) {
}
}
Present as sheet on your SwiftUI View
在您的 SwiftUI
View
上显示为表格
struct Example: View {
@State private var present = false
var body: some View {
Button("Present") {
present = true
}
.sheet(isPresented: $present) {
HalfSheet {
Text("Hello, World!")
}
}
}
}
From Lorem Ipsum Answer is work great on iPhone but not currently for iPad .来自 Lorem Ipsum 的答案在 iPhone 上效果很好,但目前不适用于 iPad 。
UISheetPresentationController
for iPhoneUISheetPresentationController
popover(isPresented:attachmentAnchor:arrowEdge:content:)
for iPadpopover(isPresented:attachmentAnchor:arrowEdge:content:)
guard UIDevice.current.userInterfaceIdiom == .phone else {
viewModel.isPresentedPopOver
return
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.