Отключить анимацию кадра SwiftUI при появлении

Цель

Скажем, у меня есть Listили LazyVGridкоторый отображает несколько элементов, вложенных в файл . я использую ForEachview для создания отдельных представлений элементов:

      ForEach(items) { item in
    ItemView(item)
}

Массив может быть @Stateсвойство на самом представлении или @Publishedсвойство в модели представления, которая соответствует @ObservableObject(Я пойду с первым в этом примере).

Теперь, когда я изменяю массив, вставляя или удаляя элементы, я хочу, чтобы изменения анимировались определенным образом, поэтому я добавляю transitionи animationмодификатор следующим образом:

      ScrollView {
    LazyVGrid(columns: 2) {
        ForEach(items) { item in
            ItemView(item)
                .transition(.scale)
        }
    }
}
.animation(.default, value: items)

Это прекрасно работает.

Проблема

Единственная загвоздка в том, что этот код также вызывает ScrollViewдля масштабирования от нуля до полного размера при первом появлении представления. (Это имеет смысл, так как массив элементов изначально пуст до того, как элементы будут извлечены из хранилища, поэтому массив действительно изменяется.)

Попытка решения

Чтобы решить проблему, мне, очевидно, нужно сделать анимацию зависимой от свойства, которое не меняется до появления представления и загрузки массива элементов. Поэтому я создал такое свойство как простое логическое значение и переключал его всякий раз, когда itemsмассив изменяется, но только после didAppearбыл назван:

      @State var changedState: Bool = false
@State var didAppear: Bool = false

@State var items: [Item] = [] {
    didSet {
        if didAppear {
            changedState.toggle()
        }
    }
}

Затем я меняю valueмодификатора анимации к этому новому свойству:

      .animation(.default, value: changedState)

✅ Это решает проблему. Тем не менее, это кажется очень «уродливым» и похоже на много накладных расходов.

Вопрос

Есть ли другой (более элегантный/краткий) способ отключить анимацию начального масштаба?


‍ Редактировать: пример минимального кода

      struct ContentView: View {
    
    @State var items: [Int] = []
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: [GridItem(), GridItem()]) {
                    ForEach(items, id: \.self) { item in
                        Rectangle()
                            .frame(height: 50)
                            .foregroundColor(.red)
                            .transition(.scale)
                    }
                }
            }
            .animation(.default, value: items)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        let newItem = items.last.map { $0 + 1 } ?? 0
                        items.append(newItem)
                    } label: {
                        Text("Add Item")
                    }
                }
            }
        }
        .onAppear {
            items = [Int](0...10)
        }
    }
}

Вот как выглядит начальная анимация:

2 ответа

Ваш didSetне будет работать так, как вы ожидаете, поэтому у нас есть .onChange(), но, как вы и подозревали, действительно есть более простой способ. Вам нужно только анимировать добавление элементов в список (который отображается на экране). Самый простой способ сделать это — добавить @Statebool и используйте его для .animation()ценность. Затем вы просто переключаете его в своей кнопке, когда добавляете в массив следующим образом:

      struct ContentView: View {
    @State var items: [Int] = []
    @State var animate = false // Variable for animation
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(), GridItem()]) {
                ForEach(items, id: \.self) { item in
                    Rectangle()
                        .frame(height: 50)
                        .foregroundColor(.red)
                        .transition(.scale)
                }
            }
        }
        // Use animate as a flag to allow items to be the value
        // for .animation
        .animation(.default, value: (animate ? items : []))
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    let newItem = items.last.map { $0 + 1 } ?? 0
                    items.append(newItem)
                    animate.toggle() // <- Switch it here
                } label: {
                    Text("Add Item")
                }
            }
        }
        .onAppear {
            items = [Int](0...10)
            // The DispatchQueue is necessary to delay changing
            // the flag until the initial view is loaded.
            DispatchQueue.main.asyncAfter(deadline: .now()) {
                animate = true
            }
        }
    }
}

Редактировать:

Приведенный выше код был изменен, чтобы отразить комментарий. Это должно соответствовать вашим потребностям.

Я обнаружил, что применяя .animationмодификатор к LazyVGridвместо ScrollViewработает так, как вы ожидаете.

      struct ContentView: View {
    
    @State var items: [Int] = []
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: [GridItem(), GridItem()]) {
                    ForEach(items, id: \.self) { item in
                        Rectangle()
                            .frame(height: 50)
                            .foregroundColor(.red)
                            .transition(.scale)
                    }
                }
                .animation(.default, value: items) // <- New Place
            }
            // <- Old Place
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        let newItem = items.last.map { $0 + 1 } ?? 0
                        items.append(newItem)
                    } label: {
                        Text("Add Item")
                    }
                }
            }
        }
        .onAppear {
            items = [Int](0...10)
        }
    }
}
Другие вопросы по тегам