在 SwiftUI 中将模态表的大小从一半更改为完整动画

Geo*_*org 5 uikit ios swift swiftui

我希望某个视图作为半屏工作表开始,但是当完成一些加载后,它应该转换为全屏工作表。

所以在 UIKit 中我的解决方案是:

let vc = UIViewController()
vc.view.backgroundColor = .red
vc.sheetPresentationController?.detents = [.medium(), .large()]
self.present(vc, animated: true)

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
    vc.sheetPresentationController?.animateChanges {
        vc.sheetPresentationController?.selectedDetentIdentifier = .large
    }
}
Run Code Online (Sandbox Code Playgroud)

这在 UIKit 中工作得很好……但这在 SwiftUI 中也可能吗?

SwiftUI 中的类似内容可能如下所示:

    struct ContentView: View {
    @State var showSheet = false
    
    var body: some View {
        Button {
            showSheet = true
        } label: {
            Text("Show-Sheet")
        }
        .sheet(isPresented: $showSheet, content: {
            SheetView()
        })
    }
}

struct SheetView: View {
    @State var showFullSheet = false
    
    var body: some View {
        Button {
            withAnimation {
                showFullSheet.toggle()
            }
        } label: {
            Text("Toggle")
        }
        .presentationDetents(showFullSheet ? [.large] : [.medium, .large])
    }
}
Run Code Online (Sandbox Code Playgroud)

这基本上是有效的,但问题是一半和完整之间的过渡不是动画的。不过我需要它动画。这在 SwiftUI 中可以解决吗?因为我猜想将我呈现的视图包装在 UIViewController 中,然后托管我现有的 SwiftUI 视图将不是一个选项,因为呈现部分仍然由 swift-ui 完成,对吧?

Nir*_*v D 11

首先使用selection绑定presentationDetents(_:selection:) 来跟踪您的selectedDetent状态。您面临此动画问题的原因是,当您设置sheetlarge仅允许设置制动装置时[.large],它不会动画并直接跳到大。为了解决这个问题,我添加了@State属性来更新允许带有presentationDetents修饰符的制动装置。现在,如果您detents set在更新状态后立即更新selectedDetent,那么它也不会显示动画,因此为了解决此问题,我添加了一秒的更新延迟detents,这将解决您的动画问题。

struct SheetView: View {
    @State var selectedDetent: PresentationDetent = .medium
    
    @State var detents: Set<PresentationDetent> = [.large, .medium]
    
    var body: some View {
        Button {
            selectedDetent = selectedDetent == .large ? .medium : .large
        } label: {
            Text("Toggle")
        }
        .presentationDetents(detents, selection: $selectedDetent)
        .onChange(of: selectedDetent) { newValue in
            if newValue == .large {
                updateDetentsWithDelay()
            } else {
                detents = [.large, .medium]
            }
        }
    }
    
    func updateDetentsWithDelay() {
        Task {
            //(1 second = 1_000_000_000 nanoseconds)
            try? await Task.sleep(nanoseconds: 100_000_000)
            guard selectedDetent == .large else { return }
            detents = [.large]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)