Transaction

温馨提示:本文最后更新于2025-02-05 11:14:32,某些文章具有时效性,若有错误或已失效,请在下方留言

当前状态处理更新的上下文。

transaction(value:_:)

使用.transaction 修饰符来更改或替换视图中使用的动画。考虑一个由按钮控制的三个相同视图,同时改变所有三个的情况:

  1. 第一个视图通过旋转“Rotation”文本视图360度来进行动画处理。
  2. 第二个视图使用 transaction(_:)修饰符来改变动画,通过在动画开始时添加两秒的延迟,然后将“Rotation\nModified”文本视图动画的旋转速度增加两倍。
  3. 第三个视图使用 transaction(_:)修饰符来禁用影响“Animation\nReplaced”文本视图的动画。

以下代码实现了这些动画:

struct TransactionExample: View {
    @State var flag = false

    var body: some View {
        VStack(spacing: 50) {
            HStack(spacing: 30) {
                Text("Rotation")
                    .rotationEffect(Angle(degrees: flag ? 360 : 0))
                Text("Rotation\nModified")
                    .rotationEffect(Angle(degrees: flag ? 360 : 0))
                    .transaction(value: flag) { t in
                        t.animation = t.animation?.delay(2.0).speed(2)
                    }

                Text("Animation\nReplaced")
                    .rotationEffect(Angle(degrees: flag ? 360 : 0))
                    .transaction(value: flag) { t in
                        t.disablesAnimations = true
                    }
            }

            Button("Animate") {
                withAnimation(.easeIn(duration: 2.0)) {
                    flag.toggle()
                }
            }
        }
    }
}
动画效果
动画效果

考虑由一个按钮控制的三个相同的动画,同时执行所有三个动画:

  1. 第一个动画将”Rotation”文本视图旋转360度。
  2. 第二个动画使用 transaction(_:)修饰符来改变动画,通过在动画开始时添加两秒的延迟,然后将”Rotation\nModified”文本视图动画的旋转速度增加两倍。
  3. 第三个动画使用 transaction(_:)修饰符来替换影响”Animation\nReplaced”文本视图的旋转动画为弹簧动画。

以下代码实现了这些动画:

struct TransactionExample: View {
    @State private var flag = false
    var body: some View {
        VStack(spacing: 50) {
            HStack(spacing: 30) {
                Text("Rotation")
                    .rotationEffect(Angle(degrees: self.flag ? 360 : 0))
                Text("Rotation\nModified")
                    .rotationEffect(Angle(degrees: self.flag ? 360 : 0))
                    .transaction { view in
                        view.animation =
                            view.animation?.delay(2.0).speed(2)
                    }

                Text("Animation\nReplaced")
                    .rotationEffect(Angle(degrees: self.flag ? 360 : 0))
                    .transaction { view in
                        view.animation = .interactiveSpring(
                            response: 0.60,
                            dampingFraction: 0.20,
                            blendDuration: 0.25
                        )
                    }
            }
            Button("Animate") {
                withAnimation(.easeIn(duration: 2.0)) {
                    self.flag.toggle()
                }
            }
        }
    }
}
动画效果
动画效果

withTransaction

您可以使用此属性来关闭可能正在显示模态展示的窗口,方法是使用 .destructive 值:

struct DismissWindowButton: View {
    @Environment(\.dismissWindow) private var dismissWindow

    var body: some View {
        Button("Close Auxiliary Window") {
            withTransaction(\.dismissBehavior, .destructive) {
                dismissWindow(id: "auxiliary")
            }
        }
    }
}

使用 witnTransaction 修饰符实现一个滚动到底部的动画。

struct NamedFont: Identifiable {
    let name: String
    var id = UUID()
}
struct ScrollTargetAnchorView: View {
    private let namedFonts: [NamedFont] = [
        NamedFont(name: "Large Title"),
        NamedFont(name: "Title"),
        NamedFont(name: "Headline"),
        NamedFont(name: "Body"),
        NamedFont(name: "Caption")
        // ...
    ]
    @State private var position: NamedFont.ID?
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(namedFonts) { item in
                    Text(item.name)
                }
            }
            .scrollTargetLayout()
        }
        .scrollPosition(id: $position)
        .safeAreaInset(edge: .bottom) {
            Button("Scroll To Bottom") {
                withAnimation {
                    withTransaction(\.scrollTargetAnchor, .bottom) {
                        position = namedFonts.last?.id
                    }
                }
            }
        }
    }
}

过渡添加和删除视图

默认情况下,SwiftUI 使用淡入淡出动画来插入或删除视图,但是如果需要,可以通过将 transition()修饰符附加到视图来更改它。

例如,我们可以使几种文本视图以不同的方式过渡,如下所示:

struct ContentView: View {
    @State private var showDetails = false

    var body: some View {
        VStack {
            Button("Press to show details") {
                withAnimation {
                    showDetails.toggle()
                }
            }

            if showDetails {
                // 从底部移入
                Text("Details go here.")
                    .transition(.move(edge: .bottom))

                // 从前导位置移入,移出到尾部边缘
                Text("Details go here.")
                    .transition(.slide)

                // 从小开始,逐渐增长到全尺寸
                Text("Details go here.")
                    .transition(.scale)
            }
        }
    }
}

创建不对称过渡

我们可以创建一个使用非对称过渡的文本视图,这样它在添加时从前边缘移入,而在删除时则向下移至底部边缘,如下所示:

struct ContentView: View {
    @State private var showDetails = false

    var body: some View {
        VStack {
            Button("Press to show details") {
                withAnimation {
                    showDetails.toggle()
                }
            }

            if showDetails {
                Text("Details go here.")
                    .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .bottom)))
            }
        }

    }
}

创建自定义过渡

尽管SwiftUI内置了多种选择,但如果我们愿意,也可以编写完全自定义的过渡。该过程分三个步骤:

  1. 创建一个 ViewModifier 来表示您处于任何状态的过渡。
  2. 创建一个 AnyTransition 扩展,将您的视图修饰符用于活动状态和身份状态。
  3. 使用 transition() 修饰符将该过波应用于视图。

例如,我们可以编写形状和视图修改器组合,以模仿 Keynote 中的 Iris 动画-它使新的幻灯片出现在向上增长的圆圈中,有点像旧日的 Looney Tunes 简介序列。

为了演示这一点,我将向您展示一个完整的代码示例,该示例执行以下操作:

  1. 定义 ScaledCircle 形状,该形状在根据某些可动画化数据进行缩放的矩形内部创建一个圆。
  2. 创建一个自定义 ViewModifier 结构,以将任何形状(在我们的示例中为缩放圆)应用为另一个视图的剪辑形状。
  3. 将其包装在 AnyTransition 扩展中,以将该修饰符包装在过渡中,以便于访问。
  4. 创建一个 swiftUI 视图以演示我们的过波过程。

这是代码,并附有注释以说明发生了什么:

struct ScaledCircle: Shape {
    // 这可控制绘图矩形内部的圆的大小。 
    // 值为0时,圆圈是不可见的;
    // 值为1时,圆圈将填充矩形。
    var animatableData: CGFloat

    func path(in rect: CGRect) -> Path {
        let maximumCircleRadius = sqrt(rect.width * rect.width + rect.height * rect.height)
        let circleRadius = maximumCircleRadius * animatableData

        let x = rect.midX - circleRadius / 2
        let y = rect.midY - circleRadius / 2

        let circleRect = CGRect(x: x, y: y, width: circleRadius, height: circleRadius)

        return Circle().path(in: circleRect)
    }
}

// 可以使用任何形状剪切任何视图的常规修改器。
struct ClipShapeModifier<T: Shape>: ViewModifier {
    let shape: T

    func body(content: Content) -> some View {
        content.clipShape(shape)
    }
}

// 结合了 ScaledCircle 和 ClipShapeModifier 的自定义过渡。
extension AnyTransition {
    static var iris: AnyTransition {
        .modifier(
            active: ClipShapeModifier(shape: ScaledCircle(animatableData: 0)),
            identity: ClipShapeModifier(shape: ScaledCircle(animatableData: 1))
        )
    }
}

// 使用我们的转场显示和隐藏红色矩形的示例视图移动。
struct ContentView: View {
    @State private var isShowingRed = false

    var body: some View {
        ZStack {
            Color.blue
                .frame(width: 200, height: 200)

            if isShowingRed {
                Color.red
                    .frame(width: 200, height: 200)
                    .transition(.iris)
                    .zIndex(1)
            }
        }
        .padding(50)
        .onTapGesture {
            withAnimation(.easeInOut) {
                isShowingRed.toggle()
            }
        }
    }
}
演示效果
演示效果

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容