diff --git a/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/SDWebImageSwiftUI.xcodeproj/project.pbxproj index 6d4d746..e3c34dc 100644 --- a/SDWebImageSwiftUI.xcodeproj/project.pbxproj +++ b/SDWebImageSwiftUI.xcodeproj/project.pbxproj @@ -75,6 +75,14 @@ 32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; }; 32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; }; 32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; }; + 32CBA78025E4D7D800C6A8DC /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; }; + 32CBA78125E4D7D800C6A8DC /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; }; + 32CBA78225E4D7D800C6A8DC /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; }; + 32CBA78325E4D7D800C6A8DC /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; }; + 32CBA78425E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */; }; + 32CBA78525E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */; }; + 32CBA78625E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */; }; + 32CBA78725E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */; }; 32D26A022446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; }; 32D26A032446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; }; 32D26A042446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; }; @@ -137,6 +145,8 @@ 32C43E2922FD586200BE87F5 /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/tvOS/SDWebImage.framework; sourceTree = ""; }; 32C43E2D22FD586E00BE87F5 /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/watchOS/SDWebImage.framework; sourceTree = ""; }; 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDWebImageSwiftUI.swift; sourceTree = ""; }; + 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePlayer.swift; sourceTree = ""; }; + 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUICompatibility.swift; sourceTree = ""; }; 32D26A012446B546005905DA /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 32ED4825242A13030053338E /* ImageManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManagerTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -279,6 +289,8 @@ 32B933E323659A0700BB7CAD /* Transition */, 326099472362E09E006EBB22 /* Indicator */, 32C43DDC22FD54C600BE87F5 /* ImageManager.swift */, + 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */, + 32CBA77F25E4D7D800C6A8DC /* SwiftUICompatibility.swift */, 32C43DDE22FD54C600BE87F5 /* WebImage.swift */, 32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */, 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */, @@ -696,6 +708,8 @@ buildActionMask = 2147483647; files = ( 32B933E523659A1900BB7CAD /* Transition.swift in Sources */, + 32CBA78025E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, + 32CBA78425E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */, 32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */, 326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84822363350C0011BDFB /* Indicator.swift in Sources */, @@ -713,6 +727,8 @@ buildActionMask = 2147483647; files = ( 32B933E623659A1900BB7CAD /* Transition.swift in Sources */, + 32CBA78125E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, + 32CBA78525E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */, 32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */, 326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84832363350C0011BDFB /* Indicator.swift in Sources */, @@ -730,6 +746,8 @@ buildActionMask = 2147483647; files = ( 32B933E723659A1900BB7CAD /* Transition.swift in Sources */, + 32CBA78225E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, + 32CBA78625E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */, 32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */, 326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84842363350C0011BDFB /* Indicator.swift in Sources */, @@ -747,6 +765,8 @@ buildActionMask = 2147483647; files = ( 32B933E823659A1900BB7CAD /* Transition.swift in Sources */, + 32CBA78325E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, + 32CBA78725E4D7D800C6A8DC /* SwiftUICompatibility.swift in Sources */, 32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */, 326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84852363350C0011BDFB /* Indicator.swift in Sources */, diff --git a/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift b/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift new file mode 100644 index 0000000..6da5092 --- /dev/null +++ b/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift @@ -0,0 +1,83 @@ +/* + * This file is part of the SDWebImage package. + * (c) DreamPiggy + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import Foundation +import SwiftUI + +#if os(iOS) || os(tvOS) || os(macOS) + +@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +struct PlatformAppear: PlatformViewRepresentable { + let appearAction: () -> Void + let disappearAction: () -> Void + + #if os(iOS) || os(tvOS) + func makeUIView(context: Context) -> some UIView { + let view = PlatformAppearView() + view.appearAction = appearAction + view.disappearAction = disappearAction + return view + } + + func updateUIView(_ uiView: UIViewType, context: Context) {} + #endif + #if os(macOS) + func makeNSView(context: Context) -> some NSView { + let view = PlatformAppearView() + view.appearAction = appearAction + view.disappearAction = disappearAction + return view + } + + func updateNSView(_ nsView: NSViewType, context: Context) {} + #endif +} + +@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +class PlatformAppearView: PlatformView { + var appearAction: () -> Void = {} + var disappearAction: () -> Void = {} + + #if os(iOS) || os(tvOS) + override func willMove(toWindow newWindow: UIWindow?) { + if newWindow != nil { + appearAction() + } else { + disappearAction() + } + } + #endif + + #if os(macOS) + override func viewWillMove(toWindow newWindow: NSWindow?) { + if newWindow != nil { + appearAction() + } else { + disappearAction() + } + } + #endif +} + +#endif + +extension View { + /// Used UIKit/AppKit behavior to detect the SwiftUI view's visibility. + /// This hack is because of SwiftUI 1.0/2.0 buggy behavior. The built-in `onAppear` and `onDisappear` is so massive on some cases. Where UIKit/AppKit is solid. + /// - Parameters: + /// - appear: The action when view appears + /// - disappear: The action when view disappears + /// - Returns: Some view + func onPlatformAppear(appear: @escaping () -> Void = {}, disappear: @escaping () -> Void = {}) -> some View { + #if os(iOS) || os(tvOS) || os(macOS) + return self.background(PlatformAppear(appearAction: appear, disappearAction: disappear)) + #else + return self.onAppear(perform: appear).onDisappear(perform: disappear) + #endif + } +} diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 934445f..5a3b916 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -65,23 +65,22 @@ public struct WebImage : View { if isAnimating && !imageManager.isIncremental { if imagePlayer.currentFrame != nil { configure(image: imagePlayer.currentFrame!) - .onAppear { - imagePlayer.startPlaying() - } - .onDisappear { + .onPlatformAppear(appear: { + self.imagePlayer.startPlaying() + }, disappear: { if self.pausable { - imagePlayer.pausePlaying() + self.imagePlayer.pausePlaying() } else { - imagePlayer.stopPlaying() + self.imagePlayer.stopPlaying() } if self.purgeable { - imagePlayer.clearFrameBuffer() + self.imagePlayer.clearFrameBuffer() } - } + }) } else { configure(image: imageManager.image!) .onReceive(imageManager.$image) { image in - imagePlayer.setupPlayer(image: image) + self.imagePlayer.setupPlayer(image: image) } } } else { @@ -94,7 +93,7 @@ public struct WebImage : View { } else { setupPlaceholder() .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) - .onAppear { + .onPlatformAppear(appear: { // Load remote image when first appear if self.imageManager.isFirstLoad { self.imageManager.load() @@ -105,14 +104,13 @@ public struct WebImage : View { if self.imageManager.image == nil && !self.imageManager.isIncremental { self.imageManager.load() } - } - .onDisappear { + }, disappear: { guard self.cancelOnDisappear else { return } // When using prorgessive loading, the previous partial image will cause onDisappear. Filter this case if self.imageManager.image == nil && !self.imageManager.isIncremental { self.imageManager.cancel() } - } + }) } } }