Skip to content

Commit 85a648f

Browse files
authored
Merge pull request #32 from SDWebImage/feature_convenient_transition_API
Refactory and introduced the convenient transition API on WebImage, compatible for SwiftUI Animation
2 parents b62cf93 + 634f8a8 commit 85a648f

File tree

5 files changed

+97
-24
lines changed

5 files changed

+97
-24
lines changed

Example/SDWebImageSwiftUIDemo/ContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ struct ContentView: View {
9898
WebImage(url: URL(string:url))
9999
.resizable()
100100
.indicator(.activity)
101+
.animation(.easeInOut(duration: 0.5))
102+
.transition(.fade)
101103
.scaledToFit()
102104
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
103-
.animation(.easeInOut(duration: 0.5))
104-
.transition(.opacity)
105105
#else
106106
WebImage(url: URL(string:url))
107107
.resizable()

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ let package = Package(
6464
- [x] Supports placeholder and detail options control for image loading as SDWebImage
6565
- [x] Supports success/failure/progress changes event for custom handling
6666
- [x] Supports indicator with activity/progress indicator and customization
67+
- [x] Supports built-in animation and transition, powered by SwiftUI
6768

6869
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.
6970

@@ -75,6 +76,8 @@ var body: some View {
7576
}
7677
.resizable()
7778
.indicator(.activity) // Activity Indicator
79+
.animation(.easeInOut(duration: 0.5))
80+
.transition(.fade) // Fade Transition
7881
.scaledToFit()
7982
.frame(width: 300, height: 300, alignment: .center)
8083
}
@@ -90,6 +93,7 @@ var body: some View {
9093
.onFailure { error in
9194
// Error
9295
}
96+
.indicator(SDWebImageActivityIndicator.medium) // Activity Indicator
9397
.transition(.fade) // Fade Transition
9498
.scaledToFit()
9599
// Data
@@ -104,11 +108,11 @@ var body: some View {
104108

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

111-
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.
115+
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.
112116

113117
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.
114118

SDWebImageSwiftUI.xcodeproj/project.pbxproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
3232
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
3333
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
34+
32B933E523659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
35+
32B933E623659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
36+
32B933E723659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
37+
32B933E823659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
3438
32C43DE622FD54CD00BE87F5 /* SDWebImageSwiftUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C43DE422FD54CD00BE87F5 /* SDWebImageSwiftUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
3539
32C43DEA22FD577300BE87F5 /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43DE922FD577300BE87F5 /* SDWebImage.framework */; };
3640
32C43DEB22FD577300BE87F5 /* SDWebImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43DE922FD577300BE87F5 /* SDWebImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -115,6 +119,7 @@
115119
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
116120
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
117121
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
122+
32B933E423659A1900BB7CAD /* Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = "<group>"; };
118123
32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
119124
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
120125
32C43DDE22FD54C600BE87F5 /* WebImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebImage.swift; sourceTree = "<group>"; };
@@ -186,6 +191,14 @@
186191
path = Indicator;
187192
sourceTree = "<group>";
188193
};
194+
32B933E323659A0700BB7CAD /* Transition */ = {
195+
isa = PBXGroup;
196+
children = (
197+
32B933E423659A1900BB7CAD /* Transition.swift */,
198+
);
199+
path = Transition;
200+
sourceTree = "<group>";
201+
};
189202
32C43DC222FD540D00BE87F5 = {
190203
isa = PBXGroup;
191204
children = (
@@ -219,6 +232,7 @@
219232
32C43DDB22FD54C600BE87F5 /* Classes */ = {
220233
isa = PBXGroup;
221234
children = (
235+
32B933E323659A0700BB7CAD /* Transition */,
222236
326099472362E09E006EBB22 /* Indicator */,
223237
324F61C4235E07EC003973B8 /* ObjC */,
224238
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
@@ -443,6 +457,7 @@
443457
isa = PBXSourcesBuildPhase;
444458
buildActionMask = 2147483647;
445459
files = (
460+
32B933E523659A1900BB7CAD /* Transition.swift in Sources */,
446461
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
447462
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */,
448463
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
@@ -459,6 +474,7 @@
459474
isa = PBXSourcesBuildPhase;
460475
buildActionMask = 2147483647;
461476
files = (
477+
32B933E623659A1900BB7CAD /* Transition.swift in Sources */,
462478
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
463479
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */,
464480
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
@@ -475,6 +491,7 @@
475491
isa = PBXSourcesBuildPhase;
476492
buildActionMask = 2147483647;
477493
files = (
494+
32B933E723659A1900BB7CAD /* Transition.swift in Sources */,
478495
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
479496
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */,
480497
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
@@ -491,6 +508,7 @@
491508
isa = PBXSourcesBuildPhase;
492509
buildActionMask = 2147483647;
493510
files = (
511+
32B933E823659A1900BB7CAD /* Transition.swift in Sources */,
494512
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
495513
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */,
496514
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* This file is part of the SDWebImage package.
3+
* (c) DreamPiggy <lizhuoli1126@126.com>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
import SwiftUI
10+
11+
extension AnyTransition {
12+
13+
/// Fade-in transition
14+
public static var fade: AnyTransition {
15+
let insertion = AnyTransition.opacity
16+
let removal = AnyTransition.identity
17+
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
18+
}
19+
20+
/// Flip from left transition
21+
public static var flipFromLeft: AnyTransition {
22+
let insertion = AnyTransition.move(edge: .leading)
23+
let removal = AnyTransition.identity
24+
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
25+
}
26+
27+
/// Flip from right transition
28+
public static var flipFromRight: AnyTransition {
29+
let insertion = AnyTransition.move(edge: .trailing)
30+
let removal = AnyTransition.identity
31+
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
32+
}
33+
34+
/// Flip from top transition
35+
public static var flipFromTop: AnyTransition {
36+
let insertion = AnyTransition.move(edge: .top)
37+
let removal = AnyTransition.identity
38+
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
39+
}
40+
41+
/// Flip from bottom transition
42+
public static var flipFromBottom: AnyTransition {
43+
let insertion = AnyTransition.move(edge: .bottom)
44+
let removal = AnyTransition.identity
45+
return AnyTransition.asymmetric(insertion: insertion, removal: removal)
46+
}
47+
}

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import SwiftUI
1010
import SDWebImage
1111

1212
public struct WebImage : View {
13+
static var emptyImage = Image(platformImage: PlatformImage())
14+
1315
var url: URL?
1416
var placeholder: Image?
1517
var options: SDWebImageOptions
@@ -30,32 +32,34 @@ public struct WebImage : View {
3032
self.options = options
3133
self.context = context
3234
self.imageManager = ImageManager(url: url, options: options, context: context)
35+
// load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
36+
// this can ensure we load the image, SDWebImage take care of the duplicated query
37+
self.imageManager.load()
3338
}
3439

3540
public var body: some View {
36-
let image: Image
3741
if let platformImage = imageManager.image {
38-
image = Image(platformImage: platformImage)
42+
var image = Image(platformImage: platformImage)
43+
image = configurations.reduce(image) { (previous, configuration) in
44+
configuration(previous)
45+
}
46+
let view = image
47+
return AnyView(view)
3948
} else {
40-
if let placeholder = placeholder {
41-
image = placeholder
42-
} else {
43-
image = Image(platformImage: PlatformImage())
49+
var image = placeholder ?? WebImage.emptyImage
50+
image = configurations.reduce(image) { (previous, configuration) in
51+
configuration(previous)
4452
}
45-
// load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :)
46-
// this can ensure we load the image, SDWebImage take care of the duplicated query
47-
self.imageManager.load()
48-
}
49-
return configurations.reduce(image) { (previous, configuration) in
50-
configuration(previous)
51-
}
52-
.onAppear {
53-
if self.imageManager.image == nil {
54-
self.imageManager.load()
53+
let view = image
54+
.onAppear {
55+
if self.imageManager.image == nil {
56+
self.imageManager.load()
57+
}
5558
}
56-
}
57-
.onDisappear {
58-
self.imageManager.cancel()
59+
.onDisappear {
60+
self.imageManager.cancel()
61+
}
62+
return AnyView(view)
5963
}
6064
}
6165
}

0 commit comments

Comments
 (0)