Fix iOS 13 compatibility
Revert back the onPlatformAppear to fix iOS 14+ behavior Use backport for all OSs
This commit is contained in:
parent
83d46c08b5
commit
e1c32aea7d
|
@ -101,15 +101,7 @@ final class AnimatedImageConfiguration: ObservableObject {
|
|||
/// A Image View type to load image from url, data or bundle. Supports animated and static image format.
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
public struct AnimatedImage : PlatformViewRepresentable {
|
||||
@SwiftUI.StateObject var imageModel_SwiftUI = AnimatedImageModel()
|
||||
@Backport.StateObject var imageModel_Backport = AnimatedImageModel()
|
||||
var imageModel: AnimatedImageModel {
|
||||
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
|
||||
return imageModel_SwiftUI
|
||||
} else {
|
||||
return imageModel_Backport
|
||||
}
|
||||
}
|
||||
@ObservedObject var imageModel: AnimatedImageModel
|
||||
@ObservedObject var imageHandler = AnimatedImageHandler()
|
||||
@ObservedObject var imageLayout = AnimatedImageLayout()
|
||||
@ObservedObject var imageConfiguration = AnimatedImageConfiguration()
|
||||
|
@ -186,11 +178,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
|||
|
||||
init(imageModel: AnimatedImageModel, isAnimating: Binding<Bool>) {
|
||||
self._isAnimating = isAnimating
|
||||
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
|
||||
_imageModel_SwiftUI = SwiftUI.StateObject(wrappedValue: imageModel)
|
||||
} else {
|
||||
_imageModel_Backport = Backport.StateObject(wrappedValue: imageModel)
|
||||
}
|
||||
_imageModel = ObservedObject(wrappedValue: imageModel)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
|
|
|
@ -28,37 +28,24 @@ public final class ImageManager : ObservableObject {
|
|||
/// true means during incremental loading
|
||||
@Published public var isIncremental: Bool = false
|
||||
|
||||
var manager: SDWebImageManager?
|
||||
weak var currentOperation: SDWebImageOperation? = nil
|
||||
|
||||
var url: URL?
|
||||
var options: SDWebImageOptions = []
|
||||
var context: [SDWebImageContextOption : Any]? = nil
|
||||
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
|
||||
var failureBlock: ((Error) -> Void)?
|
||||
var progressBlock: ((Int, Int) -> Void)?
|
||||
|
||||
/// Create a image manager for loading the specify url, with custom options and context.
|
||||
public init() {}
|
||||
|
||||
/// Start to load the url operation
|
||||
/// - Parameter url: The image url
|
||||
/// - Parameter options: The options to use when downloading the image. See `SDWebImageOptions` for the possible values.
|
||||
/// - Parameter context: A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
|
||||
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) {
|
||||
self.url = url
|
||||
self.options = options
|
||||
self.context = context
|
||||
if let manager = context?[.customManager] as? SDWebImageManager {
|
||||
self.manager = manager
|
||||
public func load(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) {
|
||||
let manager: SDWebImageManager
|
||||
if let customManager = context?[.customManager] as? SDWebImageManager {
|
||||
manager = customManager
|
||||
} else {
|
||||
self.manager = .shared
|
||||
}
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
/// Start to load the url operation
|
||||
public func load() {
|
||||
guard let manager = manager else {
|
||||
return
|
||||
manager = .shared
|
||||
}
|
||||
if currentOperation != nil {
|
||||
return
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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(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
|
||||
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.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
|
||||
}
|
||||
}
|
|
@ -9,6 +9,15 @@
|
|||
import SwiftUI
|
||||
import SDWebImage
|
||||
|
||||
/// Data Binding Object, only properties in this object can support changes from user with @State and refresh
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
final class WebImageModel : ObservableObject {
|
||||
/// URL image
|
||||
@Published var url: URL?
|
||||
@Published var webOptions: SDWebImageOptions = []
|
||||
@Published var webContext: [SDWebImageContextOption : Any]? = nil
|
||||
}
|
||||
|
||||
/// Completion Handler Binding Object, supports dynamic @State changes
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
final class WebImageHandler: ObservableObject {
|
||||
|
@ -43,6 +52,9 @@ public struct WebImage : View {
|
|||
/// True to start animation, false to stop animation.
|
||||
@Binding public var isAnimating: Bool
|
||||
|
||||
/// A observed object to pass through the image model to manager
|
||||
@ObservedObject var imageModel: WebImageModel
|
||||
|
||||
/// A observed object to pass through the image handler to manager
|
||||
@ObservedObject var imageHandler = WebImageHandler()
|
||||
|
||||
|
@ -52,25 +64,10 @@ public struct WebImage : View {
|
|||
/// A observed object to pass through the image manager loading status to indicator
|
||||
@ObservedObject var indicatorStatus = IndicatorStatus()
|
||||
|
||||
@SwiftUI.StateObject var imagePlayer_SwiftUI = ImagePlayer()
|
||||
@Backport.StateObject var imagePlayer_Backport = ImagePlayer()
|
||||
var imagePlayer: ImagePlayer {
|
||||
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
|
||||
return imagePlayer_SwiftUI
|
||||
} else {
|
||||
return imagePlayer_Backport
|
||||
}
|
||||
}
|
||||
@ObservedObject var imagePlayer = ImagePlayer()
|
||||
|
||||
@SwiftUI.StateObject var imageManager_SwiftUI = ImageManager()
|
||||
@Backport.StateObject var imageManager_Backport = ImageManager()
|
||||
var imageManager: ImageManager {
|
||||
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
|
||||
return imageManager_SwiftUI
|
||||
} else {
|
||||
return imageManager_Backport
|
||||
}
|
||||
}
|
||||
// FIXME: Use SwiftUI StateObject and remove onPlatformAppear once drop iOS 13 support
|
||||
@Backport.StateObject var imageManager = ImageManager()
|
||||
|
||||
/// Create a web image with url, placeholder, custom options and context. Optional can support animated image using Binding.
|
||||
/// - Parameter url: The image url
|
||||
|
@ -86,11 +83,11 @@ public struct WebImage : View {
|
|||
context[.animatedImageClass] = SDAnimatedImage.self
|
||||
}
|
||||
}
|
||||
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
|
||||
_imageManager_SwiftUI = SwiftUI.StateObject(wrappedValue: ImageManager(url: url, options: options, context: context))
|
||||
} else {
|
||||
_imageManager_Backport = Backport.StateObject(wrappedValue: ImageManager(url: url, options: options, context: context))
|
||||
}
|
||||
let imageModel = WebImageModel()
|
||||
imageModel.url = url
|
||||
imageModel.webOptions = options
|
||||
imageModel.webContext = context
|
||||
_imageModel = ObservedObject(wrappedValue: imageModel)
|
||||
}
|
||||
|
||||
/// Create a web image with url, placeholder, custom options and context.
|
||||
|
@ -128,24 +125,24 @@ public struct WebImage : View {
|
|||
}
|
||||
} else {
|
||||
setupPlaceholder()
|
||||
.onAppear {
|
||||
.onPlatformAppear(appear: {
|
||||
self.imageManager.successBlock = self.imageHandler.successBlock
|
||||
self.imageManager.failureBlock = self.imageHandler.failureBlock
|
||||
self.imageManager.progressBlock = self.imageHandler.progressBlock
|
||||
// Load remote image when first appear
|
||||
self.imageManager.load()
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.webOptions, context: imageModel.webContext)
|
||||
guard self.imageConfiguration.retryOnAppear else { return }
|
||||
// When using prorgessive loading, the new partial image will cause onAppear. Filter this case
|
||||
if self.imageManager.image == nil && !self.imageManager.isIncremental {
|
||||
self.imageManager.load()
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.webOptions, context: imageModel.webContext)
|
||||
}
|
||||
}.onDisappear {
|
||||
}, disappear: {
|
||||
guard self.imageConfiguration.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()
|
||||
}
|
||||
}.onReceive(imageManager.objectWillChange) { _ in
|
||||
}).onReceive(imageManager.objectWillChange) { _ in
|
||||
indicatorStatus.isLoading = imageManager.isLoading
|
||||
indicatorStatus.progress = imageManager.progress
|
||||
}
|
||||
|
@ -228,7 +225,7 @@ public struct WebImage : View {
|
|||
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
|
||||
if let placeholder = placeholder {
|
||||
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
|
||||
if imageManager.options.contains(.delayPlaceholder) && imageManager.isLoading {
|
||||
if imageModel.webOptions.contains(.delayPlaceholder) && imageManager.isLoading {
|
||||
return AnyView(configure(image: .empty))
|
||||
} else {
|
||||
return placeholder
|
||||
|
|
|
@ -18,7 +18,7 @@ class ImageManagerTests: XCTestCase {
|
|||
func testImageManager() throws {
|
||||
let expectation = self.expectation(description: "ImageManager usage with Combine")
|
||||
let imageUrl = URL(string: "https://via.placeholder.com/500x500.jpg")
|
||||
let imageManager = ImageManager(url: imageUrl)
|
||||
let imageManager = ImageManager()
|
||||
imageManager.setOnSuccess { image, cacheType, data in
|
||||
XCTAssertNotNil(image)
|
||||
expectation.fulfill()
|
||||
|
@ -29,7 +29,7 @@ class ImageManagerTests: XCTestCase {
|
|||
imageManager.setOnProgress { receivedSize, expectedSize in
|
||||
|
||||
}
|
||||
imageManager.load()
|
||||
imageManager.load(url: imageUrl)
|
||||
XCTAssertNotNil(imageManager.currentOperation)
|
||||
let sub = imageManager.objectWillChange
|
||||
.subscribe(on: RunLoop.main)
|
||||
|
|
Loading…
Reference in New Issue