Support AnimatedImage with `aspectRatio`, `antialiased`, `interpolation`, `renderingMode` and `resizable` methods, some of them is not fully implemented

This commit is contained in:
DreamPiggy 2019-10-01 14:34:11 +08:00
parent e8939701e6
commit 611ab6125e
7 changed files with 223 additions and 21 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

@ -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,111 @@ 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 aspectRatio = imageLayout.aspectRatio {
// Not implements
}
// 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.updateConstraintsIfNeeded()
view.needsDisplay = true
#else
view.updateConstraintsIfNeeded()
view.setNeedsDisplay()
#endif
}
public func image(_ image: SDAnimatedImage?) -> Self {
@ -90,15 +152,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 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 scaledToFill() -> Self {
imageLayout.contentMode = .fill
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 +219,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

@ -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

@ -15,6 +15,12 @@ typealias PlatformImage = NSImage
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)

View File

@ -81,3 +81,17 @@ extension 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