swift 在同一视图上有条件地设置两个不同的过渡

czfnxgou  于 2023-09-30  发布在  Swift
关注(0)|答案(2)|浏览(98)

你好!
我有两个按钮,一个是X,一个是取消。根据我按下的按钮,我希望X按钮有不同的过渡。
按X时:我想删除的X按钮是。不透明。动画(。默认)
当按下“取消”时:我希望X按钮的移除是.move(edge:.trailing).combined(with:.opacity).animation(.default)
对于上下文,它是一个textField,当我输入一些东西时,X按钮会出现。
我已经尝试使用State变量,在Button操作中的withAnimation中设置过渡。这似乎为下一个周期/刷新而不是当前周期/刷新设置了转换。我也研究过交易,但我只能从那里操纵动画?
所以当我按下X按钮时,下一次我按下X或取消时,它将是我设置的过渡。而当我按下取消时,下一次它会将过渡设置为他们的另一个。

正如您在GIF中看到的,它循环切换,但我希望为每个按钮设置它们。
预期行为:
当按X时,我希望它使用. opacity淡出。
当按下“取消”时,我希望X按钮“滑出”,使用.move作为删除过渡。

import Combine
import Foundation
import SwiftUI

struct SearchBar: View {
    @Binding var searchField: String
    let clearSearchField: () -> Void
    let isFocus: Bool
    let setFocus: (Bool) -> Void
    @State var transition: AnyTransition = .opacity.animation(.default)

    var body: some View {
        HStack(spacing: 16) {
            ZStack {
                Color.gray
                VStack(alignment: .leading) {
                    HStack {

                        TextField(
                            "Placeholder",
                            text: $searchField,
                            onEditingChanged: { _ in setFocus(true) }
                        )
                        .foregroundColor(.white)

                        Spacer()

                        if searchField.isNonEmpty {
                            Button {
                                transition = .opacity.animation(.default)
                                withAnimation {
                                    clearSearchField()
                                }

                            } label: {
                                Text("X")
                            }
                            .transition(
                                .asymmetric(
                                    insertion: .opacity.animation(.default),
                                    removal: transition
                                )
                            )
                        }
                    }
                }
                .padding(16)
            }
            .cornerRadius(8)

            if isFocus {
                Button(
                    action: {
                        transition = .move(edge: .trailing).combined(with: .opacity)
                        hideKeyboard()
                        clearSearchField()
                        setFocus(false)

                    },
                    label: {
                        Text("Cancel")
                            .foregroundColor(.white)
                    }
                )
                .transition(.move(edge: .trailing).combined(with: .opacity).animation(.default))
            }
        }
        .frame(height: 48)
        .foregroundColor(searchField.isEmpty ? .grey: .white)
        .padding(16)
        .animation(.default, value: [isFocus])
    }
}

这可能吗?
感谢任何帮助!

brccelvz

brccelvz1#

根据我的观察,你不可能改变一个观点的转变而不改变它的身份。一旦视图出现了一个过渡,你不能告诉它消失与另一个过渡。
所以解决这个问题的一种方法是有 * 两个 * X按钮,一个带有.opacity转换,另一个带有.move。使用@State来决定显示哪个按钮:
范例:

@State var showButtons = false
@State var shouldMove = false

var body: some View {
    VStack {
        // imagine this is your text field
        Rectangle().frame(width: 100, height: 100).onTapGesture {
            withAnimation {
                showButtons = true
            }
        }
        Spacer()
        if showButtons {
            // make buttons with different transitions
            // if shouldMove, show the button with the move transition
            // otherwise show the button with the opacity transition
            if shouldMove {
                makeButton(transition: .asymmetric(insertion: .opacity, removal: .move(edge: .trailing)))
            } else {
                makeButton(transition: .opacity)
            }
            
            Button("Cancel") {
                shouldMove = true
                withAnimation {
                    showButtons.toggle()
                }
            }
        }
    }.frame(height: 300)
}

@ViewBuilder func makeButton(transition: AnyTransition) -> some View {
    Button("X") {
        shouldMove = false
        withAnimation {
            showButtons.toggle()
        }
    }.transition(transition)
}

另一种方法是在每次想要更改过渡时为X按钮提供一个新的id,从而创建一个“新”视图。

@State var showButtons = false
@State var transition: AnyTransition = .opacity
@State var buttonId = UUID()

var body: some View {
    VStack {
        Rectangle().frame(width: 100, height: 100).onTapGesture {
            withAnimation {
                showButtons = true
            }
        }
        Spacer()
        if showButtons {
            Button("X") {
                transition = .opacity
                buttonId = UUID() // new id!
                withAnimation {
                    showButtons.toggle()
                }
            }
            .transition(transition)
            .id(buttonId)
            
            Button("Cancel") {
                transition = .asymmetric(insertion: .opacity, removal: .move(edge: .trailing))
                buttonId = UUID() // new id!
                withAnimation {
                    showButtons.toggle()
                }
            }
        }
    }.frame(height: 300)
}
y0u0uwnf

y0u0uwnf2#

解决这个问题的一种方法是在物品周围放置多层容器。不同的容器可以具有不同的过渡。
这表明它正在工作:

struct ContentView: View {

    enum ExitTransitionType {
        case opacity
        case move
    }

    @State private var exitType: ExitTransitionType?

    var body: some View {
        VStack {
            HStack {
                if exitType != .move {
                    HStack {
                        if exitType != .opacity {
                            HStack {
                                Spacer()
                                Button("Cancel") { exitType = .move }
                            }
                            .overlay {
                                Button { exitType = .opacity } label: {
                                    Image(systemName: "xmark")
                                        .resizable()
                                        .scaledToFit()
                                        .padding(12)
                                        .foregroundColor(Color(UIColor.secondaryLabel))
                                        .frame(width: 44, height: 44)
                                        .contentShape(Rectangle())
                                        .accessibilityAddTraits(.isButton)
                                }
                            }
                            .padding(30)
                            .transition(.opacity)
                        }
                    }
                    .transition(.move(edge: .trailing))
                }
            }
            .frame(minHeight: 100)

            if exitType != nil {
                Button("Reset") { exitType = nil }
                    .buttonStyle(.borderedProminent)
                    .transition(.scale)
            }
        }
        .animation(.easeInOut(duration: 0.8), value: exitType)
    }
}

在不透明度过渡的情况下,周围的容器(将具有移动过渡)仍然存在,但为空。如果你也想隐藏它,那么你可以有一个额外的布尔标志,比如containersAreVisible,你可以在嵌套内容的onDisappear回调中将其设置为false。
如果您只希望部分内容消失(比如X按钮,而不是Cancel按钮),那么您可以将Cancel按钮移动到更高一级的容器中,以便它包含在一个过渡中,但从另一个过渡中排除。
另一个例子是这个方法的实际应用,参见SwiftUI bi-directional move transition moving the wrong way in certain cases的解决方案。

相关问题