ios - 在复杂的 UI 中使用 SwiftUI 的matchedGeometryEffect
问题描述
在我的应用程序中,我想在卡片和全屏覆盖之间创建一个“英雄”动画,这matchedGeometryEffect
似乎很适合。但是,无论我尝试什么,我都无法让动画按预期工作,而且它看起来一点也不像通常的matchedGeometryEffect
动画。这是到目前为止的样子。这是我目前所拥有的:(为大量代码道歉,但这是必要的,因为对于一个简单的视图来说,它工作正常)
东西.swift
struct Something: Identifiable {
let id = UUID()
let image: Image
}
内容视图.swift
struct ContentView: View {
@Namespace var namespace
let items: [Something] = [
Image("a"), Image("b")
].map { Something(image: $0 )}
@State var selectedItem: Something?
var body: some View {
ZStack {
VStack {
ScrollView {
VStack(alignment: .leading) {
ForEach(items) { item in
CardView(
image: item.image,
namespace: namespace,
isSource: self.selectedItem == nil,
id: item.id
)
.background(Color.white)
.contentShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.zIndex(1)
.onTapGesture {
withAnimation(.spring()) {
self.selectedItem = item
}
}
}
}
}
}
.overlay(EmptyView())
if let item = selectedItem {
EventView(
image: item.image
) {
self.selectedItem = nil
}
.matchedGeometryEffect(id: item.id, in: namespace, isSource: false)
.zIndex(2)
}
}
.animation(.spring())
.transition(.scale)
}
}
CardView.swift
struct CardView: View {
let image: Image
let namespace: Namespace.ID
let isSource: Bool
let id: UUID
var body: some View {
VStack(alignment: .leading) {
ZStack(alignment: .bottomTrailing) {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 225)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.matchedGeometryEffect(id: id, in: namespace, isSource: isSource)
}
}
}
}
事件视图.swift
struct EventView: View {
let image: Image
let onDismiss: () -> Void
var body: some View {
image
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.onTapGesture(perform: onDismiss)
}
}
任何关于添加或更改以使其正常工作的建议将非常感谢,谢谢!
解决方案
我为同样的要求所做的是添加properties: .position
到matchedGeometryEffect
. 然后,您需要指定“如何”从一个视图(例如缩略图卡片视图)转换到另一个视图(例如全屏卡片视图)。这是通过自定义转换完成的,例如:
extension AnyTransition
{
// This transition will pass a value (0.0 - 1.0), indicating how much of the
// transition has passed. To communicate with the view, it will
// use the custom environment key .modalTransitionPercent
// it will also make sure the transitioning view is not faded in or out and it
// stays visible at all times.
static var modal: AnyTransition
{
AnyTransition.modifier(active: ThumbnailExpandedModifier(pct: 0), identity: ThumbnailExpandedModifier(pct: 1))
}
struct ThumbnailExpandedModifier: AnimatableModifier
{
var pct: CGFloat
var animatableData: CGFloat
{
get { pct }
set { pct = newValue }
}
func body(content: Content) -> some View
{
return content
.environment(\.modalTransitionPercent, pct)
.opacity(1)
}
}
}
extension EnvironmentValues
{
var modalTransitionPercent: CGFloat
{
get { return self[ModalTransitionKey.self] }
set { self[ModalTransitionKey.self] = newValue }
}
}
public struct ModalTransitionKey: EnvironmentKey
{
public static let defaultValue: CGFloat = 0
}
推荐阅读
- javascript - 如何在 setInterval 闭包之外访问最新的钩子状态
- python - 如何将 Node.js 转换为 python
- r - 用 R 中另一个数据集的信息填充一个变量
- google-apps-script - 用于根据单元格值添加行的 Google 表格脚本
- android - 选项卡控制器和默认选项卡控制器之间的区别
- extjs - MODERN - 对话框未按 ESC 键关闭
- android - 在同一个 google play dev 帐户上为多个应用程序使用相同的代码
- python - Sympy,分段求和,简化
- javascript - 尝试使用 useEffect 挂钩来实现此功能
- android - 如何在 Android 9 或 10 中启用 Wifi Tethering