Skip to content

Refactory and introduced the convenient transition API on WebImage, compatible for SwiftUI Animation #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Example/SDWebImageSwiftUIDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ struct ContentView: View {
WebImage(url: URL(string:url))
.resizable()
.indicator(.activity)
.animation(.easeInOut(duration: 0.5))
.transition(.fade)
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
.animation(.easeInOut(duration: 0.5))
.transition(.opacity)
#else
WebImage(url: URL(string:url))
.resizable()
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let package = Package(
- [x] Supports placeholder and detail options control for image loading as SDWebImage
- [x] Supports success/failure/progress changes event for custom handling
- [x] Supports indicator with activity/progress indicator and customization
- [x] Supports built-in animation and transition, powered by SwiftUI

Note: This `WebImage` using `Image` for internal implementation, which is the best compatible for SwiftUI layout and animation system. But it supports static image format only, because unlike `UIImageView` in UIKit, SwiftUI's `Image` does not support animation.

Expand All @@ -75,6 +76,8 @@ var body: some View {
}
.resizable()
.indicator(.activity) // Activity Indicator
.animation(.easeInOut(duration: 0.5))
.transition(.fade) // Fade Transition
.scaledToFit()
.frame(width: 300, height: 300, alignment: .center)
}
Expand All @@ -90,6 +93,7 @@ var body: some View {
.onFailure { error in
// Error
}
.indicator(SDWebImageActivityIndicator.medium) // Activity Indicator
.transition(.fade) // Fade Transition
.scaledToFit()
// Data
Expand All @@ -104,11 +108,11 @@ var body: some View {

- [x] Supports network image as well as local data and bundle image
- [x] Supports animation control using the SwiftUI Binding
- [x] Supports indicator and transition powered by SDWebImage and CoreAnimation
- [x] Supports indicator and transition, powered by SDWebImage and Core Animation
- [x] Supports advanced control like loop count, incremental load, buffer size
- [x] Supports coordinate with native UIKit/AppKit/WKInterface view
- [x] Supports coordinate with native UIKit/AppKit/WatchKit view

Note: `AnimatedImage` supports both image url or image data for animated image format. Which use the SDWebImage's [Animated ImageView](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for internal implementation. Pay attention that since this base on UIKit/AppKit representable, if you need advanced customized layout and animation, you need CoreAnimation to help.
Note: `AnimatedImage` supports both image url or image data for animated image format. Which use the SDWebImage's [Animated ImageView](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for internal implementation. Pay attention that since this base on UIKit/AppKit representable, some advanced SwiftUI layout and animation system may not work as expected. You may need UIKit/AppKit and Core Animation to modify the native view.

Note: From v0.4.0, `AnimatedImage` supports watchOS as well. However, it's not backed by SDWebImage's [Animated ImageView](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) like iOS/tvOS/macOS. It use some tricks and hacks because of the limitation on current Apple's API. It also use Image/IO decoding system, which means it supports GIF and APNG format only, but not external format like WebP.

Expand Down
18 changes: 18 additions & 0 deletions SDWebImageSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
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 */; };
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 */; };
32B933E823659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.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, ); }; };
Expand Down Expand Up @@ -115,6 +119,7 @@
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.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>"; };
32C43DDE22FD54C600BE87F5 /* WebImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebImage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -186,6 +191,14 @@
path = Indicator;
sourceTree = "<group>";
};
32B933E323659A0700BB7CAD /* Transition */ = {
isa = PBXGroup;
children = (
32B933E423659A1900BB7CAD /* Transition.swift */,
);
path = Transition;
sourceTree = "<group>";
};
32C43DC222FD540D00BE87F5 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -219,6 +232,7 @@
32C43DDB22FD54C600BE87F5 /* Classes */ = {
isa = PBXGroup;
children = (
32B933E323659A0700BB7CAD /* Transition */,
326099472362E09E006EBB22 /* Indicator */,
324F61C4235E07EC003973B8 /* ObjC */,
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
Expand Down Expand Up @@ -443,6 +457,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32B933E523659A1900BB7CAD /* Transition.swift in Sources */,
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
Expand All @@ -459,6 +474,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32B933E623659A1900BB7CAD /* Transition.swift in Sources */,
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
Expand All @@ -475,6 +491,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32B933E723659A1900BB7CAD /* Transition.swift in Sources */,
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
Expand All @@ -491,6 +508,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32B933E823659A1900BB7CAD /* Transition.swift in Sources */,
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
Expand Down
47 changes: 47 additions & 0 deletions SDWebImageSwiftUI/Classes/Transition/Transition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 SwiftUI

extension AnyTransition {

/// Fade-in transition
public static var fade: AnyTransition {
let insertion = AnyTransition.opacity
let removal = AnyTransition.identity
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
}

/// Flip from left transition
public static var flipFromLeft: AnyTransition {
let insertion = AnyTransition.move(edge: .leading)
let removal = AnyTransition.identity
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
}

/// Flip from right transition
public static var flipFromRight: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
let removal = AnyTransition.identity
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
}

/// Flip from top transition
public static var flipFromTop: AnyTransition {
let insertion = AnyTransition.move(edge: .top)
let removal = AnyTransition.identity
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
}

/// Flip from bottom transition
public static var flipFromBottom: AnyTransition {
let insertion = AnyTransition.move(edge: .bottom)
let removal = AnyTransition.identity
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
}
}
42 changes: 23 additions & 19 deletions SDWebImageSwiftUI/Classes/WebImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import SwiftUI
import SDWebImage

public struct WebImage : View {
static var emptyImage = Image(platformImage: PlatformImage())

var url: URL?
var placeholder: Image?
var options: SDWebImageOptions
Expand All @@ -30,32 +32,34 @@ public struct WebImage : View {
self.options = options
self.context = context
self.imageManager = ImageManager(url: url, options: options, context: context)
// load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
// this can ensure we load the image, SDWebImage take care of the duplicated query
self.imageManager.load()
}

public var body: some View {
let image: Image
if let platformImage = imageManager.image {
image = Image(platformImage: platformImage)
var image = Image(platformImage: platformImage)
image = configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
let view = image
return AnyView(view)
} else {
if let placeholder = placeholder {
image = placeholder
} else {
image = Image(platformImage: PlatformImage())
var image = placeholder ?? WebImage.emptyImage
image = configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
// load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
// this can ensure we load the image, SDWebImage take care of the duplicated query
self.imageManager.load()
}
return configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
.onAppear {
if self.imageManager.image == nil {
self.imageManager.load()
let view = image
.onAppear {
if self.imageManager.image == nil {
self.imageManager.load()
}
}
}
.onDisappear {
self.imageManager.cancel()
.onDisappear {
self.imageManager.cancel()
}
return AnyView(view)
}
}
}
Expand Down