From b2933b8c75b2187b4cfd6a19695060e365300333 Mon Sep 17 00:00:00 2001 From: Hugo Saynac Date: Wed, 1 Nov 2023 18:57:13 +0100 Subject: [PATCH] Fix flickering issues when resizing window (#1644) * Fix flickering issues when resizing window, or hiding notifications on macOS * Restore processor and add debouncing to the processor updates * Fix indentation * Add LazyResizableImage to the Design system module --- .../Views/LazyResizableImage.swift | 42 +++++++++++++++++++ .../Row/Subviews/StatusRowCardView.swift | 32 +++++++------- .../Subviews/StatusRowMediaPreviewView.swift | 8 ++-- 3 files changed, 59 insertions(+), 23 deletions(-) create mode 100644 Packages/DesignSystem/Sources/DesignSystem/Views/LazyResizableImage.swift diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/LazyResizableImage.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/LazyResizableImage.swift new file mode 100644 index 00000000..cc5f9c0d --- /dev/null +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/LazyResizableImage.swift @@ -0,0 +1,42 @@ +// +// LazyResizableImage.swift +// +// +// Created by Hugo Saynac on 28/10/2023. +// + +import Nuke +import NukeUI +import SwiftUI + +/// A LazyImage (Nuke) with a geometry reader under the hood in order to use a Resize Processor to optimize performances on lists. +/// This views also allows smooth resizing of the images by debouncing the update of the ImageProcessor. +struct LazyResizableImage: View { + init(url: URL?, @ViewBuilder content: @escaping (LazyImageState, GeometryProxy) -> Content) { + self.imageURL = url + self.content = content + } + + let imageURL: URL? + @State private var resizeProcessor: ImageProcessors.Resize? + @State private var debouncedTask: Task? + + @ViewBuilder + private var content: (LazyImageState, _ proxy: GeometryProxy) -> Content + + var body: some View { + GeometryReader { proxy in + LazyImage(url: imageURL) { state in + content(state, proxy) + } + .processors([resizeProcessor == nil ? .resize(size: proxy.size) : resizeProcessor!]) + .onChange(of: proxy.size, initial: true) { oldValue, newValue in + debouncedTask?.cancel() + debouncedTask = Task { + do { try await Task.sleep(for: .milliseconds(200)) } catch { return } + resizeProcessor = .resize(size: newValue) + } + } + } + } +} diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift index 640f40bb..5e865033 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift @@ -47,27 +47,23 @@ public struct StatusRowCardView: View { if let title = card.title, let url = URL(string: card.url) { VStack(alignment: .leading) { if let imageURL = card.image, !isInCaptureMode { - GeometryReader { proxy in + LazyResizableImage(url: imageURL) { state, proxy in let width = imageWidthFor(proxy: proxy) - let processors: [ImageProcessing] = [.resize(size: .init(width: width, height: imageHeight))] - LazyImage(url: imageURL) { state in - if let image = state.image { - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(height: imageHeight) - .frame(maxWidth: width) - .clipped() - } else if state.isLoading { - Rectangle() - .fill(Color.gray) - .frame(height: imageHeight) - } + if let image = state.image { + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(height: imageHeight) + .frame(maxWidth: width) + .clipped() + } else if state.isLoading { + Rectangle() + .fill(Color.gray) + .frame(height: imageHeight) } - .processors(processors) - // This image is decorative - .accessibilityHidden(true) } + // This image is decorative + .accessibilityHidden(true) .frame(height: imageHeight) } HStack { diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift index d7a9577f..02f5769a 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift @@ -29,7 +29,7 @@ public struct StatusRowMediaPreviewView: View { var availableWidth: CGFloat { if UIDevice.current.userInterfaceIdiom == .phone && - (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium + (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium { return sceneDelegate.windowWidth * 0.80 } @@ -217,10 +217,9 @@ public struct StatusRowMediaPreviewView: View { GeometryReader { proxy in switch type { case .image: - let width = isCompact ? imageMaxHeight : proxy.frame(in: .local).width - let processors: [ImageProcessing] = [.resize(size: .init(width: width, height: imageMaxHeight))] ZStack(alignment: .bottomTrailing) { - LazyImage(url: attachment.previewUrl ?? attachment.url) { state in + LazyResizableImage(url: attachment.previewUrl ?? attachment.url) { state, proxy in + let width = isCompact ? imageMaxHeight : proxy.frame(in: .local).width if let image = state.image { image .resizable() @@ -240,7 +239,6 @@ public struct StatusRowMediaPreviewView: View { .frame(maxWidth: width) } } - .processors(processors) if sensitive, !isInCaptureMode { cornerSensitiveButton }