Анимация перехода не работает в SwiftUI

Я пытаюсь создать действительно простую анимацию перехода, которая показывает / скрывает сообщение в центре экрана, нажав на кнопку:

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

    var body: some View {
        ZStack {
            Color.yellow

            VStack {
                Spacer()
                Button(action: {
                    withAnimation(.easeOut(duration: 3)) {
                        self.showMessage.toggle()
                    }
                }) {
                    Text("SHOW MESSAGE")
                }
            }

            if showMessage {
                Text("HELLO WORLD!")
                    .transition(.opacity)
            }
        }
    }
}

Согласно документации .transition(.opacity) анимация

Переход от прозрачного к непрозрачному при вставке и от непрозрачного к прозрачному при удалении.

сообщение должно исчезнуть, когда showMessage государственная собственность становится истинной и исчезает, когда становится ложной. Это не так в моем случае. Сообщение отображается с анимацией затухания, но оно скрывается без анимации вообще. Есть идеи?

РЕДАКТИРОВАТЬ: см. Результат в GIF ниже взяты из симулятора.

10 ответов

Проблема в том, что когда представления приходят и уходят в ZStack, их "zIndex" не остается прежним. Что происходит, так это то, что когда "showMessage" переходит от true к false, VStack с текстом "Hello World" помещается в нижнюю часть стека, и желтый цвет сразу же рисуется поверх него. На самом деле он исчезает, но это происходит за желтым цветом, поэтому вы его не видите.

Чтобы исправить это, вам нужно явно указать "zIndex" для каждого представления в стеке, чтобы они всегда оставались неизменными - вот так:

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

var body: some View {
    ZStack {
        Color.yellow.zIndex(0)

        VStack {
            Spacer()
            Button(action: {
                withAnimation(.easeOut(duration: 3)) {
                    self.showMessage.toggle()
                }
            }) {
                Text("SHOW MESSAGE")
            }
        }.zIndex(1)

        if showMessage {
            Text("HELLO WORLD!")
                .transition(.opacity)
                .zIndex(2)
        }
    }
}

}

Я пришел к выводу, что переходы непрозрачности не всегда работают. (но слайд в сочетании с анимацией будет работать..)

    .transition(.opacity) //does not always work

Если я напишу это как пользовательскую анимацию, она будет работать:

    .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) 
    .zIndex(1)

Я обнаружил ошибку в swiftUI_preview для анимации. когда вы используете анимацию перехода в коде и хотите увидеть, что в SwiftUI_preview она не будет показывать анимацию или просто показывает, когда какое-то представление исчезает вместе с анимацией. для решения этой проблемы вам просто нужно добавить свое представление в предварительный просмотр в VStack. как это:

struct test_UI: View {
    @State var isShowSideBar = false
    var body: some View {
        ZStack {
            Button("ShowMenu") {
                withAnimation {
                    isShowSideBar.toggle()
                }
                
            }
            if isShowSideBar {
                SideBarView()
                    .transition(.slide)
            }
        }
    }
}
        struct SomeView_Previews: PreviewProvider {
        static var previews: some View {
            VStack {
               SomeView()
            }
        }
    }

после этого все анимации будут происходить.

Я считаю, что это проблема холста. Этим утром я играл с переходами, и хотя они не работают на холсте, похоже, они работают в симуляторе. Попробуйте. Я сообщил об ошибке в Apple.

Мне больше нравится ответ Скотта Гриббена (см. Ниже), но поскольку я не могу удалить его (из-за зеленой галочки), я просто оставлю исходный ответ нетронутым. Я бы сказал, что считаю это ошибкой. Можно было бы ожидать, что zIndex будет неявно назначаться порядковыми представлениями, появляющимися в коде.


Чтобы обойти это, вы можете встроить оператор if в VStack.

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

    var body: some View {
        ZStack {
            Color.yellow

            VStack {
                Spacer()
                Button(action: {
                    withAnimation(.easeOut(duration: 3)) {
                        self.showMessage.toggle()
                    }
                }) {
                    Text("SHOW MESSAGE")
                }
            }

            VStack {
                if showMessage {
                    Text("HELLO WORLD!")
                        .transition(.opacity)
                }
            }
        }
    }
}

Я просто отказался от .transition. Это просто не работает. Вместо этого я анимировал смещение представления, что намного надежнее:

Сначала я создаю переменную состояния для смещения:

      @State private var offset: CGFloat = 200

Во-вторых, я установил для него смещение VStack. Затем в его .onAppear() я изменяю смещение обратно на 0 с анимацией:

              VStack{
            Spacer()
            HStack{
                Spacer()
                Image("MyImage")
            }
        }
        .offset(x: offset)
        .onAppear {
            withAnimation(.easeOut(duration: 2.5)) {
                offset = 0
            }
        }

Код ниже должен работать.

      import SwiftUI

struct SwiftUITest: View {
    
    @State private var isAnimated:Bool = false
  
    var body: some View {
        ZStack(alignment:.bottom) {
            VStack{
                Spacer()
                Button("Slide View"){
                    withAnimation(.easeInOut) {
                        isAnimated.toggle()
                    }
                    
                }
                Spacer()
                Spacer()
           
            }
            if isAnimated {
                RoundedRectangle(cornerRadius: 16).frame(height: UIScreen.main.bounds.height/2)
                    .transition(.slide)

            }
            
            
        }.ignoresSafeArea()
    }
}

struct SwiftUITest_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            SwiftUITest()
        }
    }
}

zIndexможет привести к прерыванию анимации при прерывании. Оберните вид, к которому вы хотите применить переход, вVStack, HStack или любой другой контейнер будет иметь смысл.

Пользовательские переходы непрозрачности SwiftUI

Вы можете сделать расширение дляinиoutнепрозрачность Переходы.

      import SwiftUI

extension AnyTransition {
    static var inOpacity: AnyTransition {
        AnyTransition.modifier(
                        active: OpacityModifier(opacity: 0),
                      identity: OpacityModifier(opacity: 1)
        )
    }
    static var outOpacity: AnyTransition {
        AnyTransition.modifier(
                        active: OpacityModifier(opacity: 1),
                      identity: OpacityModifier(opacity: 0)
        )
    }
}
struct OpacityModifier : ViewModifier {
    let opacity: Double
    
    func body(content: Content) -> some View {
        content.opacity(opacity)
    }
}

      struct ContentView : View {
    
    @State var isMessageVisible: Bool = false
    @State var text = Text("SwiftUI Transition Animation")
                          .font(.largeTitle)
                          .foregroundColor(.yellow)
    
    var body: some View {
        ZStack {
            Color.indigo.ignoresSafeArea()
            
            VStack {
                Spacer()
                Button("SHOW MY MESSAGE") {
                    withAnimation(.linear(duration: 2)) {
                        isMessageVisible.toggle()
                    }
                }
            }
            if isMessageVisible {
                text.transition(.inOpacity)       // in
            } else {
                text.transition(.outOpacity)      // out
            }
        }
    }
}

Вы должны поставить

       .id(showMessage) 

после тела вашего VStack, это должно вам помочь.

Другие вопросы по тегам