Trying to move the initial state setup before `onAppear` to fix the watchOS switching url or any other state issue

This maybe a behavior changes, need testing
This commit is contained in:
DreamPiggy 2024-04-29 18:19:45 +08:00
parent 8b26cb7d0d
commit 5f8a8acf09
5 changed files with 68 additions and 126 deletions

View File

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
3243AFE62AA37EFF0049A43B /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
3243AFE72AA37EFF0049A43B /* WebImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43DDE22FD54C600BE87F5 /* WebImage.swift */; };
3243AFE82AA37EFF0049A43B /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; };
3243AFE92AA37EFF0049A43B /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
@ -26,10 +25,6 @@
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
329885EE2AA37FCB0071F2BA /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 329885ED2AA37FCB0071F2BA /* SDWebImage.framework */; };
32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
32B933E523659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
32B933E623659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
32B933E723659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
@ -83,7 +78,6 @@
326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = "<group>"; };
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
329885ED2AA37FCB0071F2BA /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/visionOS/SDWebImage.framework; sourceTree = "<group>"; };
32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUICompatibility.swift; sourceTree = "<group>"; };
32B933E423659A1900BB7CAD /* Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = "<group>"; };
32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
@ -221,7 +215,6 @@
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */,
32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */,
32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */,
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */,
32D26A012446B546005905DA /* Image.swift */,
);
@ -491,7 +484,6 @@
3243AFEB2AA37EFF0049A43B /* AnimatedImage.swift in Sources */,
3243AFE82AA37EFF0049A43B /* ImagePlayer.swift in Sources */,
3243AFED2AA37EFF0049A43B /* SDWebImageSwiftUI.swift in Sources */,
3243AFE62AA37EFF0049A43B /* SwiftUICompatibility.swift in Sources */,
3243AFEE2AA37F010049A43B /* Indicator.swift in Sources */,
3243AFEA2AA37EFF0049A43B /* Image.swift in Sources */,
);
@ -507,7 +499,6 @@
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
32D26A022446B546005905DA /* Image.swift in Sources */,
@ -524,7 +515,6 @@
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
32D26A032446B546005905DA /* Image.swift in Sources */,
@ -541,7 +531,6 @@
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
32D26A042446B546005905DA /* Image.swift in Sources */,
@ -558,7 +547,6 @@
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
32D26A052446B546005905DA /* Image.swift in Sources */,

View File

@ -15,17 +15,47 @@ import SDWebImage
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
public final class ImageManager : ObservableObject {
/// loaded image, note when progressive loading, this will published multiple times with different partial image
@Published public var image: PlatformImage?
public var image: PlatformImage? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loaded image data, may be nil if hit from memory cache. This will only published once even on incremental image loading
@Published public var imageData: Data?
public var imageData: Data? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loaded image cache type, .none means from network
@Published public var cacheType: SDImageCacheType = .none
public var cacheType: SDImageCacheType = .none {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loading error, you can grab the error code and reason listed in `SDWebImageErrorDomain`, to provide a user interface about the error reason
@Published public var error: Error?
public var error: Error? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// true means during incremental loading
@Published public var isIncremental: Bool = false
public var isIncremental: Bool = false {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// A observed object to pass through the image manager loading status to indicator
@Published public var indicatorStatus = IndicatorStatus()
public var indicatorStatus = IndicatorStatus()
weak var currentOperation: SDWebImageOperation? = nil
@ -51,8 +81,8 @@ public final class ImageManager : ObservableObject {
return
}
currentURL = url
indicatorStatus.isLoading = true
indicatorStatus.progress = 0
self.indicatorStatus.isLoading = true
self.indicatorStatus.progress = 0
currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in
guard let self = self else {
return
@ -63,9 +93,7 @@ public final class ImageManager : ObservableObject {
} else {
progress = 0
}
DispatchQueue.main.async {
self.indicatorStatus.progress = progress
}
self.progressBlock?(receivedSize, expectedSize)
}) { [weak self] (image, data, error, cacheType, finished, _) in
guard let self = self else {

View File

@ -28,9 +28,21 @@ public struct Indicator<T> where T : View {
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
public class IndicatorStatus : ObservableObject {
/// whether indicator is loading or not
@Published var isLoading: Bool = false
var isLoading: Bool = false {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// indicator progress, should only be used for indicator binding, value between [0.0, 1.0]
@Published var progress: Double = 0
var progress: Double = 0 {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
}
/// A implementation detail View Modifier with indicator

View File

@ -1,92 +0,0 @@
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <lizhuoli1126@126.com>
*
* 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(watchOS)
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
struct PlatformAppear: PlatformViewRepresentable {
let appearAction: () -> Void
let disappearAction: () -> Void
#if os(iOS) || os(tvOS) || os(visionOS)
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 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
class PlatformAppearView: PlatformView {
var appearAction: () -> Void = {}
var disappearAction: () -> Void = {}
#if os(iOS) || os(tvOS)
override func willMove(toWindow newWindow: UIWindow?) {
if newWindow != nil {
DispatchQueue.main.async {
self.appearAction()
}
} else {
DispatchQueue.main.async {
self.disappearAction()
}
}
}
#endif
#if os(macOS)
override func viewWillMove(toWindow newWindow: NSWindow?) {
if newWindow != nil {
DispatchQueue.main.async {
self.appearAction()
}
} else {
DispatchQueue.main.async {
self.disappearAction()
}
}
}
#endif
}
#endif
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
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
}
}

View File

@ -163,26 +163,22 @@ public struct WebImage<Content> : View where Content: View {
}
} else {
content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)
setupPlaceholder()
setupInitialState()
// Load Logic
.onPlatformAppear(appear: {
self.setupManager()
if (self.imageManager.error == nil) {
// Load remote image when first appear
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
}
.onAppear {
guard self.imageConfiguration.retryOnAppear else { return }
// When using prorgessive loading, the new partial image will cause onAppear. Filter this case
if self.imageManager.error != nil && !self.imageManager.isIncremental {
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
}
}, disappear: {
}
.onDisappear {
guard self.imageConfiguration.cancelOnDisappear else { return }
// When using prorgessive loading, the previous partial image will cause onDisappear. Filter this case
if self.imageManager.error != nil && !self.imageManager.isIncremental {
self.imageManager.cancel()
}
})
}
}
}
}
@ -328,6 +324,16 @@ public struct WebImage<Content> : View where Content: View {
}
}
/// Initial state management (update when imageModel.url changed)
func setupInitialState() -> some View {
self.setupManager()
if (self.imageManager.error == nil) {
// Load remote image when first appear
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
}
return setupPlaceholder()
}
/// Placeholder View Support
func setupPlaceholder() -> some View {
let result = content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)