我真的很难包装新的 iOS 15UISheetPresentationController以在 SwiftUI(半模式)中使用。我明白我应该继承UIViewControllerRepresentable。根据我的自定义 ImagePicker 的示例,我无法完成这项工作。
有人可以帮忙吗?特别是我不知道如何处理presentedViewController初始化UISheetPresentationController自身所需的内容:
func makeUIViewController(context: UIViewControllerRepresentableContext<KitSheet>) -> UISheetPresentationController {
let sheet = UISheetPresentationController(presentedViewController: <#T##UIViewController#>, presenting: <#T##UIViewController?#>)
sheet.delegate = context.coordinator
return sheet
}
Run Code Online (Sandbox Code Playgroud)
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller
lor*_*sum 10
如果您想要图像选择器
import SwiftUI
///Sample usage
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerParentView: View {
@State var isPresented = false
@State var selectedImage: UIImage? = nil
var body: some View {
print("ImagePickerParentView :: \(#function) :: isPresented == \(isPresented)")
return VStack{
if selectedImage != nil{
Image(uiImage: selectedImage!)
.resizable()
.frame(width: 100, height: 100)
}
Button("present image picker", action: {
isPresented.toggle()
}).imagePicker(isPresented: $isPresented, uiImage: $selectedImage, detents: [.medium()], largestUndimmedDetentIdentifier: .large)
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
extension View {
func imagePicker(isPresented: Binding<Bool>, uiImage: Binding<UIImage?>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool = false, preferredCornerRadius: CGFloat? = nil)-> some View {
print("\(#function) :: isPresented == \(isPresented)")
return modifier(ImagePickerViewModifier(isPresented: isPresented, uiImage: uiImage, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius))
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerViewModifier: ViewModifier {
@Binding var isPresented: Bool
@Binding var uiImage: UIImage?
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let preferredCornerRadius: CGFloat?
func body(content: Content) -> some View {
print("ImagePickerViewModifier :: \(#function) :: isPresented == \(isPresented)")
return content.overlay(
AdaptiveImagePicker_UI(isPresented: $isPresented, uiImage: $uiImage, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius).frame(width: 0, height: 0)
)
.onChange(of: isPresented, perform: { value in
print("AdaptiveSheet :: onChange :: isPresented == \(value)")
})
//}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct AdaptiveImagePicker_UI: UIViewControllerRepresentable {
@Binding var isPresented: Bool
@Binding var uiImage: UIImage?
var detents : [UISheetPresentationController.Detent] = [.medium(), .large()]
var largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium
var prefersScrollingExpandsWhenScrolledToEdge: Bool = false
var prefersEdgeAttachedInCompactHeight: Bool = true
var prefersGrabberVisible: Bool = false
var preferredCornerRadius: CGFloat?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> AdaptiveImagePickerViewController {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
let vc = AdaptiveImagePickerViewController(coordinator: context.coordinator, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius)
return vc
}
func updateUIViewController(_ uiViewController: AdaptiveImagePickerViewController, context: Context) {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
print("CustomSheet_UI :: \(#function) :: context.coordinator.parent.isPresented == \(context.coordinator.parent.isPresented)")
if isPresented {
uiViewController.presentImagePicker()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: AdaptiveImagePicker_UI
var isPresented: Bool = false
init(_ parent: AdaptiveImagePicker_UI) {
print("CustomSheet_UI :: \(#function) :: parent.isPresented == \(parent.isPresented)")
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("CustomSheet_UI.Coordinator :: \(#function) :: parent.isPresented == \(parent.isPresented)")
if parent.isPresented{
parent.isPresented = false
}
}
//Adjust the variable when the user cancels
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
if parent.isPresented{
parent.isPresented = 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.isPresented = false
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
class AdaptiveImagePickerViewController: UIViewController {
var coordinator: AdaptiveImagePicker_UI.Coordinator
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let preferredCornerRadius: CGFloat?
private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
init(coordinator: AdaptiveImagePicker_UI.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool, preferredCornerRadius: CGFloat?) {
print("AdaptiveImagePickerViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
self.coordinator = coordinator
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
self.prefersGrabberVisible = prefersGrabberVisible
self.preferredCornerRadius = preferredCornerRadius
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
print("AdaptiveImagePickerViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
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 hostPopover = imagePicker.popoverPresentationController {
hostPopover.sourceView = super.view
let sheet = hostPopover.adaptiveSheetPresentationController
//As of 13 Beta 4 if .medium() is the only detent in landscape error occurs
sheet.detents = (isLandscape ? [.large()] : detents)
sheet.largestUndimmedDetentIdentifier =
largestUndimmedDetentIdentifier
sheet.prefersScrollingExpandsWhenScrolledToEdge =
prefersScrollingExpandsWhenScrolledToEdge
sheet.prefersEdgeAttachedInCompactHeight =
prefersEdgeAttachedInCompactHeight
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
sheet.prefersGrabberVisible = prefersGrabberVisible
sheet.preferredCornerRadius = preferredCornerRadius
}
present(imagePicker, animated: true, completion: nil)
}
/// To compensate for l orientation
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
isLandscape = true
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = [.large()]
} else {
isLandscape = false
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = detents
}
super.viewWillTransition(to: size, with: coordinator)
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerParentView_Previews: PreviewProvider {
static var previews: some View {
ImagePickerParentView()
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想要一个采用任何 SwiftUI 的产品,View只需要进行一些更改即可。
//This is the sample usage
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheetParentView: View {
@State var isPresented = false
var body: some View {
print("CustomSheetParentView :: \(#function) :: isPresented == \(isPresented)")
return VStack{
Button("present sheet", action: {
isPresented.toggle()
}).adaptiveSheet(isPresented: $isPresented, detents: [.medium()], largestUndimmedDetentIdentifier: .medium, disableSwipeToDismiss: false){
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .center)
.foregroundColor(.clear)
.border(Color.blue, width: 3)
.overlay(
LazyVStack{
Text("Hello, World!")
Button("dismiss", action: {
print("dismiss button :: isPresented == \(isPresented)")
isPresented = false
})
CustomSheetParentView()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
print("onTap :: isPresented == \(isPresented)")
isPresented.toggle()
}
)
.background(Color(UIColor.systemBackground))
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheetView_Previews: PreviewProvider {
static var previews: some View {
CustomSheetParentView()
}
}
//EVERYTHING from here down is Reusable and can be pasted into a project and then use `.adaptiveSheet` just like `.sheet`
@available(iOS 15.0, macCatalyst 15.0,*)
extension View {
func adaptiveSheet<T: View>(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool = false, disableSwipeToDismiss: Bool = false, preferredCornerRadius: CGFloat? = nil, @ViewBuilder content: @escaping () -> T)-> some View {
print("\(#function) :: isPresented == \(isPresented)")
return modifier(AdaptiveSheet<T>(isPresented: isPresented, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, sheetContent: content))
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct AdaptiveSheet<T: View>: ViewModifier {
@Binding var isPresented: Bool
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let disableSwipeToDismiss: Bool
let preferredCornerRadius: CGFloat?
@ViewBuilder let sheetContent: T
func body(content: Content) -> some View {
print("AdaptiveSheet :: \(#function) :: isPresented == \(isPresented)")
return content.overlay(
CustomSheet_UI(isPresented: $isPresented, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss,preferredCornerRadius: preferredCornerRadius, content: {sheetContent}).frame(width: 0, height: 0)
)
.onChange(of: isPresented, perform: { value in
print("AdaptiveSheet :: onChange :: isPresented == \(value)")
})
//}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheet_UI<T: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
var detents : [UISheetPresentationController.Detent] = [.medium(), .large()]
var largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium
var prefersScrollingExpandsWhenScrolledToEdge: Bool = false
var prefersEdgeAttachedInCompactHeight: Bool = true
var prefersGrabberVisible: Bool = false
var disableSwipeToDismiss: Bool = false
var preferredCornerRadius: CGFloat?
@ViewBuilder let content: T
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> CustomSheetViewController<T> {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
let vc = CustomSheetViewController(coordinator: context.coordinator, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, content: {content})
return vc
}
func updateUIViewController(_ uiViewController: CustomSheetViewController<T>, context: Context) {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
print("CustomSheet_UI :: \(#function) :: context.coordinator.parent.isPresented == \(context.coordinator.parent.isPresented)")
if isPresented {
uiViewController.presentModalView()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
var parent: CustomSheet_UI
var isPresented: Bool = false
init(_ parent: CustomSheet_UI) {
print("CustomSheet_UI :: \(#function) :: parent.isPresented == \(parent.isPresented)")
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("CustomSheet_UI.Coordinator :: \(#function) :: parent.isPresented == \(parent.isPresented)")
if parent.isPresented{
parent.isPresented = false
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
class CustomSheetViewController<Content: View>: UIViewController {
let content: Content
var coordinator: CustomSheet_UI<Content>.Coordinator
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let disableSwipeToDismiss: Bool
let preferredCornerRadius: CGFloat?
private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
init(coordinator: CustomSheet_UI<Content>.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool, disableSwipeToDismiss: Bool, preferredCornerRadius: CGFloat?, @ViewBuilder content: @escaping () -> Content) {
print("CustomSheetViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
self.content = content()
self.coordinator = coordinator
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
self.prefersGrabberVisible = prefersGrabberVisible
self.disableSwipeToDismiss = disableSwipeToDismiss
self.preferredCornerRadius = preferredCornerRadius
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
print("CustomSheetViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
dismiss(animated:
vau*_*all 10
发现这里给出的选项有点复杂,所以这里有 3 个步骤的替代方案:
子类化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()]
}
}
}
Run Code Online (Sandbox Code Playgroud)
UIViewControllerRepresentable使用您的创建一个UIHostingController,我们在这里使用 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) {
}
}
Run Code Online (Sandbox Code Playgroud)
在 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!")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
9691 次 |
| 最近记录: |