Merge pull request #7 from SDWebImage/feature_image_method

Feature support the Image struct method on `WebImage` and `AnimatedImage`
This commit is contained in:
DreamPiggy 2019-10-01 14:54:30 +08:00 committed by GitHub
commit 08979446b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 295 additions and 45 deletions

View File

@ -2,7 +2,7 @@ PODS:
- SDWebImage (5.1.0):
- SDWebImage/Core (= 5.1.0)
- SDWebImage/Core (5.1.0)
- SDWebImageSwiftUI (0.1.0):
- SDWebImageSwiftUI (0.1.1):
- SDWebImage (~> 5.1)
DEPENDENCIES:
@ -18,7 +18,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
SDWebImage: fb387001955223213dde14bc08c8b73f371f8d8f
SDWebImageSwiftUI: 22254f3ced4f056602cd8167b64106ab6419c6e6
SDWebImageSwiftUI: fa0b13b16a92985532cd13931b88aea4ff7efb0b
PODFILE CHECKSUM: 146734166216dd8fc1597433cc675999454ed4b2

View File

@ -15,9 +15,11 @@ struct ContentView: View {
var body: some View {
VStack {
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
.resizable()
.scaledToFit()
.frame(width: CGFloat(300), height: CGFloat(300), alignment: .center)
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"), options: [.progressiveLoad])
.resizable()
.scaledToFill()
.frame(width: CGFloat(400), height: CGFloat(300), alignment: .center)
}

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
"state": {
"branch": null,
"revision": "0a3cd255a655b73fb3b3437acf2ab506b5c0c9c6",
"version": "5.1.0"
"revision": "9c1682e37bf3486daccd313fcbcd7fd90a2064f4",
"version": "5.2.0"
}
}
]

View File

@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
32C43DE622FD54CD00BE87F5 /* SDWebImageSwiftUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C43DE422FD54CD00BE87F5 /* SDWebImageSwiftUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
32C43DEA22FD577300BE87F5 /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43DE922FD577300BE87F5 /* SDWebImage.framework */; };
32C43DEB22FD577300BE87F5 /* SDWebImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43DE922FD577300BE87F5 /* SDWebImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -85,6 +89,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.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>"; };
32C43DDE22FD54C600BE87F5 /* WebImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebImage.swift; sourceTree = "<group>"; };
@ -174,6 +179,7 @@
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */,
32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */,
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */,
);
path = Classes;
sourceTree = "<group>";
@ -385,6 +391,7 @@
files = (
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
);
@ -396,6 +403,7 @@
files = (
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
);
@ -407,6 +415,7 @@
files = (
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
);
@ -418,6 +427,7 @@
files = (
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
);

View File

@ -20,6 +20,10 @@ final class AnimatedImageModel : ObservableObject {
// Layout Binding Object
final class AnimatedImageLayout : ObservableObject {
@Published var contentMode: ContentMode = .fill
@Published var aspectRatio: CGFloat?
@Published var renderingMode: Image.TemplateRenderingMode?
@Published var interpolation: Image.Interpolation?
@Published var antialiased: Bool = false
}
// View
@ -31,53 +35,115 @@ public struct AnimatedImage : ViewRepresentable {
var webContext: [SDWebImageContextOption : Any]? = nil
#if os(macOS)
public typealias NSViewType = SDAnimatedImageView
public typealias NSViewType = AnimatedImageViewWrapper
#else
public typealias UIViewType = SDAnimatedImageView
public typealias UIViewType = AnimatedImageViewWrapper
#endif
#if os(macOS)
public func makeNSView(context: NSViewRepresentableContext<AnimatedImage>) -> SDAnimatedImageView {
public func makeNSView(context: NSViewRepresentableContext<AnimatedImage>) -> AnimatedImageViewWrapper {
makeView(context: context)
}
public func updateNSView(_ nsView: SDAnimatedImageView, context: NSViewRepresentableContext<AnimatedImage>) {
public func updateNSView(_ nsView: AnimatedImageViewWrapper, context: NSViewRepresentableContext<AnimatedImage>) {
updateView(nsView, context: context)
}
#else
public func makeUIView(context: UIViewRepresentableContext<AnimatedImage>) -> SDAnimatedImageView {
public func makeUIView(context: UIViewRepresentableContext<AnimatedImage>) -> AnimatedImageViewWrapper {
makeView(context: context)
}
public func updateUIView(_ uiView: SDAnimatedImageView, context: UIViewRepresentableContext<AnimatedImage>) {
public func updateUIView(_ uiView: AnimatedImageViewWrapper, context: UIViewRepresentableContext<AnimatedImage>) {
updateView(uiView, context: context)
}
#endif
func makeView(context: ViewRepresentableContext<AnimatedImage>) -> SDAnimatedImageView {
SDAnimatedImageView()
func makeView(context: ViewRepresentableContext<AnimatedImage>) -> AnimatedImageViewWrapper {
AnimatedImageViewWrapper()
}
func updateView(_ view: SDAnimatedImageView, context: ViewRepresentableContext<AnimatedImage>) {
view.image = imageModel.image
func updateView(_ view: AnimatedImageViewWrapper, context: ViewRepresentableContext<AnimatedImage>) {
view.wrapped.image = imageModel.image
if let url = imageModel.url {
view.sd_setImage(with: url, placeholderImage: view.image, options: webOptions, context: webContext)
view.wrapped.sd_setImage(with: url, placeholderImage: nil, options: webOptions, context: webContext)
}
layoutView(view, context: context)
}
func layoutView(_ view: AnimatedImageViewWrapper, context: ViewRepresentableContext<AnimatedImage>) {
// AspectRatio
if let _ = imageLayout.aspectRatio {
// TODO: Needs layer transform and geometry calculation
}
// ContentMode
switch imageLayout.contentMode {
case .fit:
#if os(macOS)
view.imageScaling = .scaleProportionallyUpOrDown
view.wrapped.imageScaling = .scaleProportionallyUpOrDown
#else
view.contentMode = .scaleAspectFit
view.wrapped.contentMode = .scaleAspectFit
#endif
case .fill:
#if os(macOS)
view.imageScaling = .scaleAxesIndependently
view.wrapped.imageScaling = .scaleAxesIndependently
#else
view.contentMode = .scaleToFill
view.wrapped.contentMode = .scaleToFill
#endif
}
// RenderingMode
if let renderingMode = imageLayout.renderingMode {
switch renderingMode {
case .template:
#if os(macOS)
view.wrapped.image?.isTemplate = true
#else
view.wrapped.image = view.wrapped.image?.withRenderingMode(.alwaysTemplate)
#endif
case .original:
#if os(macOS)
view.wrapped.image?.isTemplate = false
#else
view.wrapped.image = view.wrapped.image?.withRenderingMode(.alwaysOriginal)
#endif
@unknown default:
// Future cases, not implements
break
}
}
// Interpolation
if let interpolation = imageLayout.interpolation {
switch interpolation {
case .high:
view.interpolationQuality = .high
case .medium:
view.interpolationQuality = .medium
case .low:
view.interpolationQuality = .low
case .none:
view.interpolationQuality = .none
@unknown default:
// Future cases, not implements
break
}
} else {
view.interpolationQuality = .default
}
// Antialiased
view.shouldAntialias = imageLayout.antialiased
// Display
#if os(macOS)
view.needsLayout = true
view.needsDisplay = true
#else
view.setNeedsLayout()
view.setNeedsDisplay()
#endif
}
public func image(_ image: SDAnimatedImage?) -> Self {
@ -90,15 +156,49 @@ public struct AnimatedImage : ViewRepresentable {
return self
}
public func scaledToFit() -> Self {
imageLayout.contentMode = .fit
public func resizable(
capInsets: EdgeInsets = EdgeInsets(),
resizingMode: Image.ResizingMode = .stretch) -> AnimatedImage
{
return self
}
public func scaledToFill() -> Self {
imageLayout.contentMode = .fill
public func renderingMode(_ renderingMode: Image.TemplateRenderingMode?) -> AnimatedImage {
imageLayout.renderingMode = renderingMode
return self
}
public func interpolation(_ interpolation: Image.Interpolation) -> AnimatedImage {
imageLayout.interpolation = interpolation
return self
}
public func antialiased(_ isAntialiased: Bool) -> AnimatedImage {
imageLayout.antialiased = isAntialiased
return self
}
public func aspectRatio(_ aspectRatio: CGFloat? = nil, contentMode: ContentMode) -> AnimatedImage {
imageLayout.aspectRatio = aspectRatio
imageLayout.contentMode = contentMode
return self
}
public func aspectRatio(_ aspectRatio: CGSize, contentMode: ContentMode) -> AnimatedImage {
var ratio: CGFloat?
if aspectRatio.width > 0 && aspectRatio.height > 0 {
ratio = aspectRatio.width / aspectRatio.height
}
return self.aspectRatio(ratio, contentMode: contentMode)
}
public func scaledToFit() -> AnimatedImage {
self.aspectRatio(nil, contentMode: .fit)
}
public func scaledToFill() -> AnimatedImage {
self.aspectRatio(nil, contentMode: .fill)
}
}
extension AnimatedImage {
@ -123,4 +223,17 @@ extension AnimatedImage {
}
}
#if DEBUG
struct AnimatedImage_Previews : PreviewProvider {
static var previews: some View {
Group {
AnimatedImage(url: URL(string: "http://assets.sbnation.com/assets/2512203/dogflops.gif"))
.resizable()
.aspectRatio(contentMode: .fit)
.padding()
}
}
}
#endif
#endif

View File

@ -14,10 +14,10 @@ class ImageManager : ObservableObject {
var objectWillChange = PassthroughSubject<ImageManager, Never>()
private var manager = SDWebImageManager.shared
private weak var currentOperation: SDWebImageOperation? = nil
var manager = SDWebImageManager.shared
weak var currentOperation: SDWebImageOperation? = nil
var image: Image? {
var image: PlatformImage? {
willSet {
objectWillChange.send(self)
}
@ -36,11 +36,7 @@ class ImageManager : ObservableObject {
func load() {
currentOperation = manager.loadImage(with: url, options: options, context: context, progress: nil) { (image, data, error, cacheType, _, _) in
if let image = image {
#if os(macOS)
self.image = Image(nsImage: image)
#else
self.image = Image(uiImage: image)
#endif
self.image = image
}
}
}

View File

@ -0,0 +1,61 @@
/*
* 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 SDWebImage
// View Wrapper
public class AnimatedImageViewWrapper : PlatformView {
var wrapped = SDAnimatedImageView()
var interpolationQuality = CGInterpolationQuality.default
var shouldAntialias = false
override public func draw(_ rect: CGRect) {
#if os(macOS)
guard let ctx = NSGraphicsContext.current?.cgContext else {
return
}
#else
guard let ctx = UIGraphicsGetCurrentContext() else {
return
}
#endif
ctx.interpolationQuality = interpolationQuality
ctx.setShouldAntialias(shouldAntialias)
}
public override init(frame frameRect: CGRect) {
super.init(frame: frameRect)
addSubview(wrapped)
wrapped.bindFrameToSuperviewBounds()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
addSubview(wrapped)
wrapped.bindFrameToSuperviewBounds()
}
}
extension PlatformView {
/// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
/// Please note that this has no effect if its `superview` is `nil` add this `UIView` instance as a subview before calling this.
func bindFrameToSuperviewBounds() {
guard let superview = self.superview else {
print("Error! `superview` was nil call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
return
}
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true
self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true
}
}

View File

@ -9,6 +9,28 @@
import Foundation
import SwiftUI
#if os(macOS)
typealias PlatformImage = NSImage
#else
typealias PlatformImage = UIImage
#endif
#if os(macOS)
public typealias PlatformView = NSView
#else
public typealias PlatformView = UIView
#endif
extension Image {
init(platformImage: PlatformImage) {
#if os(macOS)
self.init(nsImage: platformImage)
#else
self.init(uiImage: platformImage)
#endif
}
}
#if !os(watchOS)
#if os(macOS)

View File

@ -15,6 +15,8 @@ public struct WebImage : View {
public var options: SDWebImageOptions
public var context: [SDWebImageContextOption : Any]?
var configurations: [(Image) -> Image] = []
@ObservedObject var imageManager: ImageManager
public init(url: URL?, placeholder: Image? = nil, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) {
@ -26,26 +28,70 @@ public struct WebImage : View {
}
public var body: some View {
if let image = imageManager.image {
return image
.resizable()
.onAppear {}
.onDisappear {}
} else if let image = placeholder {
return image
.resizable()
.onAppear { self.imageManager.load() }
.onDisappear { self.imageManager.cancel() }
let image: Image
if let platformImage = imageManager.image {
image = Image(platformImage: platformImage)
} else if let placeholder = placeholder {
image = placeholder
} else {
#if os(macOS)
let emptyImage = Image(nsImage: NSImage())
#else
let emptyImage = Image(uiImage: UIImage())
#endif
return emptyImage
.resizable()
.onAppear { self.imageManager.load() }
.onDisappear { self.imageManager.cancel() }
image = emptyImage
}
return configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
.onAppear {
if self.imageManager.image == nil {
self.imageManager.load()
}
}
.onDisappear {
self.imageManager.cancel()
}
}
}
extension WebImage {
func configure(_ block: @escaping (Image) -> Image) -> WebImage {
var result = self
result.configurations.append(block)
return result
}
public func resizable(
capInsets: EdgeInsets = EdgeInsets(),
resizingMode: Image.ResizingMode = .stretch) -> WebImage
{
configure { $0.resizable(capInsets: capInsets, resizingMode: resizingMode) }
}
public func renderingMode(_ renderingMode: Image.TemplateRenderingMode?) -> WebImage {
configure { $0.renderingMode(renderingMode) }
}
public func interpolation(_ interpolation: Image.Interpolation) -> WebImage {
configure { $0.interpolation(interpolation) }
}
public func antialiased(_ isAntialiased: Bool) -> WebImage {
configure { $0.antialiased(isAntialiased) }
}
}
#if DEBUG
struct WebImage_Previews : PreviewProvider {
static var previews: some View {
Group {
WebImage(url: URL(string: "https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/SDWebImage_logo.png"))
.resizable()
.aspectRatio(contentMode: .fit)
.padding()
}
}
}
#endif