SwiftUI и чрезмерная перерисовка - PullRequest
0 голосов
/ 17 апреля 2020

TL; DR :

Применение визуальных эффектов к содержимому ScrollView вызывает тысячи запросов на одно и то же (неизменное) изображение для каждого жеста перетаскивания. Могу ли я уменьшить это? (В моем реальном приложении у меня есть 50 с лишним изображений в представлении, и прокрутка соответственно sluggi sh.)

Суть

Чтобы дать немного Жизнь для прокрутки HStack изображений, я применил несколько преобразований для кругового эффекта "карусели". (Советы по созданию примера кода из Джона М. и Пола Хадсона )

Код можно запускать путем копирования-вставки, как указано. (Вам необходимо предоставить изображение.) Без двух строк, помеченных /* 1 */ и /* 2 */, объект Slide сообщает о шести запросах изображения, независимо от того, сколько вы перетаскиваете и прокручиваете. Включите две строки и наблюдайте за увеличением числа запросов до 1000 одним движением пальца.

Замечания

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

Но ... должно ли это обязательно потребовать постоянного повторного получения изображений stati c? Случайное перетаскивание моего мизинца вперед и назад вызовет десятки тысяч запросов изображений. Это кажется чрезмерным. Есть ли способ уменьшить накладные расходы в этом примере?

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

Код

import SwiftUI

// Comment out lines marked 1 & 2 and watch the request count go down.
struct ContentView: View {
  var body: some View {
    GeometryReader { outerGeo in
      ScrollView(.horizontal, showsIndicators: false) {
        HStack {
          ForEach(Slide.all) { slide in
            GeometryReader { innerGeo in
              Image(uiImage: slide.image).resizable().scaledToFit()
/* 1 */       .rotation3DEffect(.degrees(Double(innerGeo.localOffset(in: outerGeo).width) / 10), axis: (x: 0, y: 1, z: 0))
/* 2 */       .scaleEffect(1.0 - abs(innerGeo.localOffset(in: outerGeo).width) / 800.0)
            }
            .frame(width:200)
          }
        }
      }
    }
    .clipped()
    .border(Color.red, width: 4)
    .frame(width: 400, height: 200)
  }
}

// Provides images for the ScrollView. Tracks and reports image requests.
struct Slide : Identifiable {
  let id: Int
  static let all = (1...6).map(Self.init)
  static var requestCount = 0
  var image: UIImage {
    Self.requestCount += 1
    print("Request # \(Self.requestCount)")
    return UIImage(named: "blueSquare")!  // Or whatever image
  }
}

// Handy extension for finding local coords.
extension GeometryProxy {
  func localOffset(in outerGeo: GeometryProxy) -> CGSize {
    let innerFrame = self.frame(in: .global)
    let outerFrame = outerGeo.frame(in: .global)
    return CGSize(
      width : innerFrame.midX - outerFrame.midX,
      height: innerFrame.midY - outerFrame.midY
    )
  }
}

1 Ответ

0 голосов
/ 17 апреля 2020

я думаю, вы можете попробовать это так:

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

class ImageCache {
    static let slides = Slide.all

    // do prefetch your images here....
    static let cachedImage = UIImage(named: "blueSquare")!

    struct Slide : Identifiable {
        let id: Int
        static let all = (1...6).map(Self.init)
        static var requestCount = 0

        var image: UIImage {
            Self.requestCount += 1
            print("Request # \(Self.requestCount)")
            //  return ImageCache.image!  // Or whatever image
            return ImageCache.cachedImage  // Or whatever image
        }
    }

}
// Comment out lines marked 1 & 2 and watch the request count go down.
struct ContentView: View {
  var body: some View {
    GeometryReader { outerGeo in
      ScrollView(.horizontal, showsIndicators: false) {
        HStack {
            ForEach(ImageCache.slides) { slide in
            GeometryReader { innerGeo in
              Image(uiImage: slide.image).resizable().scaledToFit()
/* 1 */       .rotation3DEffect(.degrees(Double(innerGeo.localOffset(in: outerGeo).width) / 10), axis: (x: 0, y: 1, z: 0))
/* 2 */       .scaleEffect(1.0 - abs(innerGeo.localOffset(in: outerGeo).width) / 800.0)
            }
            .frame(width:200)
          }
        }
      }
    }
    .clipped()
    .border(Color.red, width: 4)
    .frame(width: 400, height: 200)
  }
}

// Provides images for the ScrollView. Tracks and reports image requests.

// Handy extension for finding local coords.
extension GeometryProxy {
  func localOffset(in outerGeo: GeometryProxy) -> CGSize {
    let innerFrame = self.frame(in: .global)
    let outerFrame = outerGeo.frame(in: .global)
    return CGSize(
      width : innerFrame.midX - outerFrame.midX,
      height: innerFrame.midY - outerFrame.midY
    )
  }
}
...