Сломанная анимация SwiftUI при использовании TabView

#ios #animation #swiftui #tabview #swiftui-geometryeffect

Вопрос:

У меня есть рабочая анимация в представлении SwiftUI, которая прерывается, когда я помещаю это представление в a TabView , переходим на другую вкладку и возвращаемся. Как я могу это исправить?

Моя точка зрения более сложна, но я свел ее к следующему:

 import SwiftUI

@main
struct AnimationTestApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                ContentView()
                    .tabItem {
                        Text("Tab 1")
                    }
                Text("Content 2")
                    .tabItem {
                        Text("Tab 2")
                    }
            }
        }
    }
}

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    @Namespace var namespace

    var body: some View {
        VStack {
            Button(action: {
                viewModel.move()
            }) {
                Text("Move")
            }

            ZStack() {
                Rectangle()
                    .fill(.gray)
                    .frame(width: 300, height: 300, alignment: .center)
                if viewModel.moved {
                    RectView()
                        .matchedGeometryEffect(id: 0, in: namespace, properties: .frame)
                        .animation(Animation.default.speed(0.5))
                        .offset(x: 0, y: -130)

                } else {
                    RectView()
                        .matchedGeometryEffect(id: 0, in: namespace, properties: .frame)
                        .animation(Animation.default.speed(0.5))
                        .offset(x: 0, y: 130)
                }
            }
        }
    }
}

struct RectView: View {
    var body: some View {
        Rectangle()
            .frame(width: 100, height: 30, alignment: .center)
    }
}


final class ViewModel: ObservableObject {
    @Published var moved = false

    func move() {
        moved = !moved
    }
}
 

Вот запись этого кода на экране. При RectView нажатии анимация плавно перемещается вверх и вниз Move . После переключения Tab 2 и возврата при первом нажатии Move он прыгает без анимации. Следующие нажатия снова анимируются.

Анимация

На самом деле модель представления более сложна. Состояние (в данном случае: moved ) изменяется через некоторое время после нажатия кнопки. Некоторая работа выполняется в фоновом потоке, а затем изменение состояния запускается в основном потоке. Вот почему я не могу перенести анимацию в Button действие.

Кроме того, вид более сложный. Он RectView удаляется из глубины иерархии представлений и добавляется где-то совсем в другом месте.

Этот метод func animation(_ animation: Animation?) устарел в iOS 15. Проблема уже существует в iOS 14 навсегда. Я также попытался удалить animation модификаторы и поместить moved = !moved их в withAnimation { } блок. Все тот же результат.

Как я мог это исправить, сохранив TabView при этом ?

Комментарии:

1. Пояснение: В моем исходном коде у меня около 40 прямых просмотров (на самом деле они сложнее, чем прямая). Только один или два из них перемещаются при изменении состояния. Они глубоко вложены в иерархию ZStacks, VStacks, GeometryReaders и т. Д. Вот почему мне нужно работать с matchedGeometryEffect и offset.

Ответ №1:

У вас была какая-то плохая проблема с кодированием, вот рабочий код, также вам здесь не нужен matchedGeometryEffect :

 import SwiftUI

@main
struct AnimationTestApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                ContentView()
                    .tabItem {
                        Text("Tab 1")
                    }
                Text("Content 2")
                    .tabItem {
                        Text("Tab 2")
                    }
            }
        }
    }
}

struct ContentView: View {
    
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        
        VStack {
            
            Button(action: { viewModel.moved.toggle() }) {
                Text("Move")
            }

            ZStack() {

                Rectangle()
                    .fill(Color.gray)
                    .frame(width: 300, height: 300, alignment: .center)
                
                RectView()
                    .offset(x: 0, y: viewModel.moved ? -130 : 130)
                    .animation(Animation.default.speed(0.5), value: viewModel.moved)
                
            }
        }
    }
}

struct RectView: View {
    var body: some View {
        Rectangle()
            .frame(width: 100, height: 30)
    }
}


final class ViewModel: ObservableObject {
    @Published var moved: Bool = false
}