Search code examples
iosanimationswiftuitransition

swiftui animation how to determine the asymmetric transition in real time


It's hard to describe my problem in words.

I want to do a selector behave like that.

enter image description here

But after I change the browse direction, because I already set the in and out transition, even if I change the transition to the new one, the view still uses the old one to disappear. (the first one and the last one)

Is there some way to solve the problem?

enter image description here

Here is the code

 @State var translations:[String] = ["未知"]
    @State var index = 0
    @State var transition:AnyTransition = .asymmetric(insertion: .move(edge: .trailing), removal:.move(edge: .leading)).combined(with: .opacity)
var transTexts:some View{
        HStack{
            if(translations.count > 1){
                Button {
                    if(index > 0){
                        transition = .asymmetric(insertion: .move(edge: .leading), removal:.move(edge: .trailing)).combined(with: .opacity)
                        disabled.toggle()
                        index -= 1
                    }
                } label: {
                    Image(systemName:"arrowtriangle.backward.circle.fill")
                }
                .disabled(index == 0)
                Text(translations[index])
                    .scroll()
                    .font(.body)
                    .id(index)
                    .disabled(disabled)
                    .transition(transition)
            }
            Spacer()
            if(translations.count  > 1){
                Button {
                    if(index < translations.count-1){
                        transition = .asymmetric(insertion: .move(edge: .trailing), removal:.move(edge: .leading)).combined(with: .opacity)
                        disabled.toggle()
                        index += 1
                    }
                } label: {
                    Image(systemName:"arrowtriangle.forward.circle.fill")
                }
                .disabled(index == translations.count - 1)
            }
        }
        .animation(.default,value: index)

Solution

  • I tried manually controlling which transition it is, by using a Toggle. The transition works correctly if I just toggled the toggle before I switched direction. e.g. go right 3 times, toggle, then go left 3 times.

    When you do this automatically with buttons, it seems like the time when index changes is "too close" to when transition changes, and doesn't have the same effect as when I do it manually.

    If you just delay the change of index by a tiny amount of time, it should work.

    transition = ...
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
        index += 1 // or -= 1
    }
    

    Interestingly, DispatchQueue.main.async { ... } doesn't work. You have to give it some time.

    I'd also suggest using a Bool or enum to encode the direction it is going in, rather than an AnyTransition.

    Working code:

    @State var translations:[String] = ["Lorem Ipsum", "Foo Bar baz", "Something Else", "AAAAA", "BBBBB"]
    @State var index = 0
    
    @State var isForward = true
    
    var body:some View{
        VStack {
            HStack{
                if(translations.count > 1){
                    Button {
                        if(index > 0){
                            isForward = false
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
                                index -= 1
                            }
                        }
                    } label: {
                        Image(systemName:"arrowtriangle.backward.circle.fill")
                    }
                    .disabled(index == 0)
                }
                
                if translations.count > 0 {
                    Text(translations[index])
                        .font(.body)
                        .id(index)
                        .transition(
                            // I did not combine with .opacity here to make it more obvious
                            isForward ?
                                .asymmetric(insertion: .move(edge: .trailing), removal:.move(edge: .leading)) :
                                    .asymmetric(insertion: .move(edge: .leading), removal:.move(edge: .trailing))
                        )
                }
                
                Spacer()
                
                if(translations.count  > 1){
                    Button {
                        if index < translations.count - 1 {
                            isForward = true
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
                                index += 1
                            }
                        }
                    } label: {
                        Image(systemName:"arrowtriangle.forward.circle.fill")
                    }
                    .disabled(index == translations.count - 1)
                }
            }
            .animation(.default,value: index)
        }
    }