perf: reduce the number of swiftui updates in ImageCarousel

not sure if it helps much, but just trying to see reduce the number of
updates overall. Trying to fix swiftui scroll performance :(
This commit is contained in:
William Casarin
2024-01-11 11:47:44 -08:00
parent 3b436f9d58
commit d591dc0a7a

View File

@@ -52,6 +52,29 @@ enum ImageShape {
} }
} }
class CarouselModel: ObservableObject {
var current_url: URL?
var fillHeight: CGFloat
var maxHeight: CGFloat
var firstImageHeight: CGFloat?
@Published var open_sheet: Bool
@Published var selectedIndex: Int
@Published var video_size: CGSize?
@Published var image_fill: ImageFill?
init(image_fill: ImageFill?) {
self.current_url = nil
self.fillHeight = 350
self.maxHeight = UIScreen.main.bounds.height * 1.2 // 1.2
self.firstImageHeight = nil
self.open_sheet = false
self.selectedIndex = 0
self.video_size = nil
self.image_fill = image_fill
}
}
// MARK: - Image Carousel // MARK: - Image Carousel
@MainActor @MainActor
struct ImageCarousel: View { struct ImageCarousel: View {
@@ -60,34 +83,22 @@ struct ImageCarousel: View {
let evid: NoteId let evid: NoteId
let state: DamusState let state: DamusState
@ObservedObject var model: CarouselModel
@State private var open_sheet: Bool = false
@State private var current_url: URL? = nil
@State private var image_fill: ImageFill? = nil
@State private var fillHeight: CGFloat = 350
@State private var maxHeight: CGFloat = UIScreen.main.bounds.height * 1.2 // 1.2
@State private var firstImageHeight: CGFloat? = nil
@State private var currentImageHeight: CGFloat?
@State private var selectedIndex = 0
@State private var video_size: CGSize? = nil
init(state: DamusState, evid: NoteId, urls: [MediaUrl]) { init(state: DamusState, evid: NoteId, urls: [MediaUrl]) {
_open_sheet = State(initialValue: false)
_current_url = State(initialValue: nil)
let media_model = state.events.get_cache_data(evid).media_metadata_model
_image_fill = State(initialValue: media_model.fill)
self.urls = urls self.urls = urls
self.evid = evid self.evid = evid
self.state = state self.state = state
let media_model = state.events.get_cache_data(evid).media_metadata_model
self._model = ObservedObject(initialValue: CarouselModel(image_fill: media_model.fill))
} }
var filling: Bool { var filling: Bool {
image_fill?.filling == true model.image_fill?.filling == true
} }
var height: CGFloat { var height: CGFloat {
firstImageHeight ?? image_fill?.height ?? fillHeight model.firstImageHeight ?? model.image_fill?.height ?? model.fillHeight
} }
func Placeholder(url: URL, geo_size: CGSize, num_urls: Int) -> some View { func Placeholder(url: URL, geo_size: CGSize, num_urls: Int) -> some View {
@@ -105,9 +116,9 @@ struct ImageCarousel: View {
} }
} }
.onAppear { .onAppear {
if self.image_fill == nil, let size = state.video.size_for_url(url) { if self.model.image_fill == nil, let size = state.video.size_for_url(url) {
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight) let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: model.maxHeight, fillHeight: model.fillHeight)
self.image_fill = fill self.model.image_fill = fill
} }
} }
} }
@@ -118,23 +129,23 @@ struct ImageCarousel: View {
case .image(let url): case .image(let url):
Img(geo: geo, url: url, index: index) Img(geo: geo, url: url, index: index)
.onTapGesture { .onTapGesture {
open_sheet = true model.open_sheet = true
} }
case .video(let url): case .video(let url):
DamusVideoPlayer(url: url, video_size: $video_size, controller: state.video) DamusVideoPlayer(url: url, video_size: $model.video_size, controller: state.video)
.onChange(of: video_size) { size in .onChange(of: model.video_size) { size in
guard let size else { return } guard let size else { return }
let fill = ImageFill.calculate_image_fill(geo_size: geo.size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight) let fill = ImageFill.calculate_image_fill(geo_size: geo.size, img_size: size, maxHeight: model.maxHeight, fillHeight: model.fillHeight)
print("video_size changed \(size)") print("video_size changed \(size)")
if self.image_fill == nil { if self.model.image_fill == nil {
print("video_size firstImageHeight \(fill.height)") print("video_size firstImageHeight \(fill.height)")
firstImageHeight = fill.height self.model.firstImageHeight = fill.height
state.events.get_cache_data(evid).media_metadata_model.fill = fill state.events.get_cache_data(evid).media_metadata_model.fill = fill
} }
self.image_fill = fill self.model.image_fill = fill
} }
} }
} }
@@ -150,7 +161,7 @@ struct ImageCarousel: View {
.configure { view in .configure { view in
view.framePreloadCount = 3 view.framePreloadCount = 3
} }
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in .imageFill(for: geo.size, max: model.maxHeight, fill: model.fillHeight) { fill in
state.events.get_cache_data(evid).media_metadata_model.fill = fill state.events.get_cache_data(evid).media_metadata_model.fill = fill
// blur hash can be discarded when we have the url // blur hash can be discarded when we have the url
// NOTE: this is the wrong place for this... we need to remove // NOTE: this is the wrong place for this... we need to remove
@@ -159,9 +170,9 @@ struct ImageCarousel: View {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
state.events.lookup_img_metadata(url: url)?.state = .not_needed state.events.lookup_img_metadata(url: url)?.state = .not_needed
} }
image_fill = fill self.model.image_fill = fill
if index == 0 { if index == 0 {
firstImageHeight = fill.height self.model.firstImageHeight = fill.height
//maxHeight = firstImageHeight ?? maxHeight //maxHeight = firstImageHeight ?? maxHeight
} else { } else {
//maxHeight = firstImageHeight ?? fill.height //maxHeight = firstImageHeight ?? fill.height
@@ -181,7 +192,7 @@ struct ImageCarousel: View {
} }
var Medias: some View { var Medias: some View {
TabView(selection: $selectedIndex) { TabView(selection: $model.selectedIndex) {
ForEach(urls.indices, id: \.self) { index in ForEach(urls.indices, id: \.self) { index in
GeometryReader { geo in GeometryReader { geo in
Media(geo: geo, url: urls[index], index: index) Media(geo: geo, url: urls[index], index: index)
@@ -189,12 +200,12 @@ struct ImageCarousel: View {
} }
} }
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.fullScreenCover(isPresented: $open_sheet) { .fullScreenCover(isPresented: $model.open_sheet) {
ImageView(video_controller: state.video, urls: urls, settings: state.settings) ImageView(video_controller: state.video, urls: urls, settings: state.settings)
} }
.frame(height: height) .frame(height: height)
.onChange(of: selectedIndex) { value in .onChange(of: model.selectedIndex) { value in
selectedIndex = value model.selectedIndex = value
} }
.tabViewStyle(PageTabViewStyle()) .tabViewStyle(PageTabViewStyle())
} }
@@ -205,7 +216,7 @@ struct ImageCarousel: View {
.onTapGesture { } .onTapGesture { }
// This is our custom carousel image indicator // This is our custom carousel image indicator
CarouselDotsView(urls: urls, selectedIndex: $selectedIndex) CarouselDotsView(urls: urls, selectedIndex: $model.selectedIndex)
} }
} }
} }