Scroll 快速和过度重画

Scroll 快速和过度重画,scroll,rotation,swiftui,redraw,Scroll,Rotation,Swiftui,Redraw,TL;博士: 将视觉效果应用于滚动视图的内容会导致对每个拖动手势的相同(不变)图像的数千个请求。我能减少这个吗?(在我真正的应用程序中,我的视图中有50多个图像,滚动速度也相应地慢了。) 要点 为了给滚动的HStack图像赋予一点生命,我应用了一些变换来实现圆形的“旋转木马”效果。(从和到示例代码的帽子提示) 该代码可以按照给定的方式复制粘贴运行。(您确实需要提供图像。)如果没有标记为/*1*/和/*2*/的两行,幻灯片对象将报告六个图像请求,无论您拖动和滚动了多少。启用这两行,只需轻轻一按手指

TL;博士

将视觉效果应用于
滚动视图的内容
会导致对每个拖动手势的相同(不变)图像的数千个请求。我能减少这个吗?(在我真正的应用程序中,我的视图中有50多个图像,滚动速度也相应地慢了。)

要点

为了给滚动的
HStack
图像赋予一点生命,我应用了一些变换来实现圆形的“旋转木马”效果。(从和到示例代码的帽子提示)

该代码可以按照给定的方式复制粘贴运行。(您确实需要提供图像。)如果没有标记为
/*1*/
/*2*/
的两行,
幻灯片
对象将报告六个图像请求,无论您拖动和滚动了多少。启用这两行,只需轻轻一按手指,就可以看到请求计数缩放到1000

备注

SwiftUI基于当前状态,以廉价的方式重新绘制轻量级
视图。对状态依赖关系的粗心管理可能会使视图树的某些部分不正确地失效。在这种情况下,滚动时的不断旋转和缩放使运行时重新呈现内容

但是。。。这是否需要不断地重新检索静态图像?随意地来回拖动我的小指将触发数以万计的图像请求。这似乎太过分了。在这个例子中,有没有减少开销的方法

当然,这是一个原始的设计,它一直在展示它的所有内容,而不是采用单元重用方法,比如说,
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
    )
  }
}

我想你可以这样试试:

不,这不是一个完整的解决方案,我只取了一个缓存图像(而不是一个缓存图像数组,你必须事先预加载),但概念应该很清楚,所以这应该很快…我认为

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
    )
  }
}

愚蠢的问题:为什么你的图像检索很昂贵?如果你把它缓存起来,它应该会非常快…我确实这么做了,这很有帮助。更重要的是,你的想法在很多情况下都是正确的。在我目前的项目中,我(到目前为止)有五种不同的缓存来存放昂贵的资产。例如,我的所有静态数据集(路径、坐标、布局)都被规范化为一个大小为1x1的包含矩形。我缓存非规范化值,因为它们在运行时不会更改。但是,我经常觉得我是在为自己抵御SwiftUI而不是随波逐流。这并没有真正回答我的问题,我的问题不是“如何加快图像检索?”而是“如何减少不必要的图像检索?”这篇文章(下半部分)很好地解释了如何使用视图id(u)来提高视图性能。也许这对你的处境会有帮助。那很有趣,谢谢你的工作犬。挑战在于将更新放置到id值的何处?在我的代码示例中,没有公开控制流的地方。这纯粹是声明性的,这应该让我在那之前忘记所有这些。也许如果我打开拖拽处理器。。。顺便说一句,我确实尝试过在一些
.id
修饰符中开槽,但没有用。也许这只是突出了预取和数据源的优势,比如
UITableView
的“旧”时代。我看到必须执行数千次操作,但不是数千次重新获取。尽管我认为SwiftUI(或我)应该(将?)更聪明一些,但明显的延迟是不可接受的。我为
UIScrollView
编写了相同的代码,并将其包装在
UIViewRepresentable
中。没有故障,没有数千次重新获取。最棘手的部分是弄清楚如何调整
cattransferm3d
。简言之:将
m14
组件的值更改±0.001。