Эффект согласованной геометрии с AsyncImage iOS 15

Рассмотрим следующий пример:

          struct ContentView: View {

    @State var showSplash: Bool = true
    @Namespace var animationNamespace

    var body: some View {
        ZStack {
            if showSplash {
                GeometryReader { geometry in
                    AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
                        image.resizable()
                        .scaledToFill()
                        .matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
                        .transition(.move(edge: .bottom))
                        .frame(width: geometry.size.width)
                        .transition(.move(edge: .bottom))
                        .edgesIgnoringSafeArea(.all)
                        .clipped()
                    } placeholder: {
                        Color.gray
                    }
                }
                .onTapGesture {
                    toggleSplashScreen(false)
                }
            } else {
                ScrollView {
                    GeometryReader { geometry in
                        AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
                            image
                            image
                                .resizable()
                                .scaledToFill()
                                .matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
                                .transition(.move(edge: .bottom))
                        } placeholder: {
                            Color.gray
                        }
                        .frame(width: geometry.size.width, height: 400)
                        .clipped()
                    }
                    .edgesIgnoringSafeArea(.all)
                    .onTapGesture {
                        toggleSplashScreen(true)
                    }
                }
            }
        }
    }
}

С помощью вспомогательного метода здесь:

      private extension ContentView {
    func toggleSplashScreen(_ toggle: Bool) {
        withAnimation(.spring(response: 0.85, dampingFraction: 0.95)) {
            showSplash = toggle
        }
    }
}

Это производит:

Я заметил здесь две вещи, которые хотел бы исправить

  1. Мигающий белый эффект при переходе между двумя состояниями.
  2. Я заметил, так как мы используем AsyncImage, когда showSplashизменяет AsyncImages, только иногда попадает в блок. В результате переход становится действительно прерывистым. Я проверил это со статическим изображением из файла ресурсов, и переход стал плавным. Я также попытался создать механизм кэширования для AsyncImage, но все еще имел проблемы с его нажатием placeholder блокировать иногда.

Хотелось бы услышать любые идеи :) Спасибо!

1 ответ

Есть несколько вещей, которые, я думаю, вы могли бы сделать, чтобы улучшить это.

Во-первых, вы немного боретесь с тем, как SwiftUI поддерживает идентичность представления. Один из способов, которыми SwiftUI определяет, когда он может повторно использовать существующую структуру, а не воссоздавать структуру, - это ее расположение в иерархии представлений. Итак, когда вы переключаете свою структуру, вы переходите от:

      GeometryReader 
  AsyncImage

к

      ScrollView
  GeometryReader
    AsyncImage

В результате система считает, что существует два представления, и поэтому каждый раз перестраивает изображение (и перезагружает изображение). Я думаю, что именно отсюда появляются ваши белые вспышки, поскольку вы видите серый заполнитель в середине анимации. Если бы вы могли оставить представление прокрутки на месте, возможно, отключив прокрутку, когда она не нужна (если это возможно), тогда ОС могла бы сохранить идентичность. (см. https://developer.apple.com/videos/play/wwdc2021/10022/)

Это подводит вас ко второй области исследования. замечательно по удобству, которое дает вам при загрузке контента из сети. К сожалению, это не ускоряет общение. Ваша цель должна заключаться в том, чтобы выходить в сеть как можно реже.

Прямо сейчас ваша стратегия изменения размера фокусируется на изменении размера изображения. Это означает, что при каждом переходе вы «попадаете в сеть» (прочтите, что ваш код идет по медленной, пыльной, грунтовой дороге). Вместо изменения размера изображения вы должны просто загрузить изображение один раз (медленная часть) и изменить размер представления, в котором оно отображается. Общая идея заключается в том, чтобы позволить загрузить изображение, а затем управлять тем, как изображение анимируется, путем анимации кадра представления.

Вот где я получаю меньше пользы. Я не знаю достаточно, чтобы понять, сможет ли он реализовать эту стратегию. Вроде так и должно быть ... но я не знаю, что это так. Возможно, вам придется прибегнуть к загрузке и сохранению изображения как состояния отдельно от представления, которое его представляет.

Поэтому я советую ограничить количество перезагрузок сетевых данных. Это включает в себя помощь SwiftUI в сохранении идентичности поэтому его не нужно перезагружать каждый раз при создании представления. И попробуйте реализовать свою анимацию и масштабирование в представлении, а не в изображении, потому что изменение масштаба изображения также требует перезагрузки сети.

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