Skip to content

Support backward deployment on iOS 12 with CocoaPods and Carthage #67

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 5 commits into from
Dec 6, 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
3 changes: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ let package = Package(
.target(
name: "SDWebImageSwiftUI",
dependencies: ["SDWebImage"],
path: "SDWebImageSwiftUI/Classes",
exclude: ["ObjC"]
path: "SDWebImageSwiftUI/Classes"
),
]
)
61 changes: 60 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ For more information, it's really recommended to check our demo, to learn detail

### Common Problems

+ Using Image/WebImage/AnimatedImage in Button/NavigationLink
#### Using Image/WebImage/AnimatedImage in Button/NavigationLink

SwiftUI's `Button` apply overlay to its content (except `Text`) by default, this is common mistake to write code like this, which cause strange behavior:

Expand Down Expand Up @@ -251,6 +251,65 @@ NavigationView {
}
```

#### Use for backward deployment and weak linking SwiftUI

SDWebImageSwiftUI supports to use when your App Target has a deployment target version less than iOS 13/macOS 10.15/tvOS 13/watchOS 6. Which will weak linking of SwiftUI(Combine) to allows writing code with available check at runtime.

To use backward deployment, you have to do the follow things:

+ Add `-weak_framework SwiftUI -weak_framework Combine` in your App Target's `Other Linker Flags` build setting

You should notice that all the third party SwiftUI framework should have this build setting as well, not only just ourself (we already added). Or when running on iOS 12 device, it will trigger the runtime dyld error on startup.

+ Use CocoaPods or Carthage (SwiftPM does not support weak linking nor backward deployment currently)

For Carthage user, the built binary framework will use [Library Evolution](https://swift.org/blog/abi-stability-and-more/) to support for backward deployment.

For CocoaPods user, you should skip the platform validation in Podfile with

```ruby
platform :ios, '13.0' # This does not effect your App Target's deployment target version, just a hint for CocoaPods
```

+ Add **all the SwiftUI code** with the available annotation and runtime check, like this:

```swift
// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ...
if #available(iOS 13, *) {
window.rootViewController = UIHostingController(rootView: contentView)
} else {
window.rootViewController = ViewController()
}
// ...
}

// ViewController.swift
class ViewController: UIViewController {
var label: UILabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(label)
self.label.text = "Hello World iOS 12!"
self.label.sizeToFit()
self.label.center = self.view.center
}
}

// ContentView.swift
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
struct ContentView : View {
var body: some View {
Group {
Text("Hello World iOS 13!")
WebImage(url: URL(string: "https://i.loli.net/2019/09/24/rX2RkVWeGKIuJvc.jpg"))
}
}
}
```

## Demo

To run the example using SwiftUI, following the steps:
Expand Down
7 changes: 6 additions & 1 deletion SDWebImageSwiftUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ It brings all your favorite features from SDWebImage, like async image loading,
s.watchos.deployment_target = '6.0'

s.source_files = 'SDWebImageSwiftUI/Classes/**/*', 'SDWebImageSwiftUI/Module/*.h'
s.pod_target_xcconfig = {
'SUPPORTS_MACCATALYST' => 'YES',
'DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER' => 'NO',
'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES'
}

s.frameworks = 'SwiftUI'
s.weak_frameworks = 'SwiftUI', 'Combine'
s.dependency 'SDWebImage', '~> 5.3'
s.swift_version = '5.1'
end
14 changes: 14 additions & 0 deletions SDWebImageSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
Expand Down Expand Up @@ -552,6 +553,12 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = (
"-weak_framework",
SwiftUI,
"-weak_framework",
Combine,
);
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -566,6 +573,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
Expand Down Expand Up @@ -611,6 +619,12 @@
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-weak_framework",
SwiftUI,
"-weak_framework",
Combine,
);
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
Expand Down
13 changes: 13 additions & 0 deletions SDWebImageSwiftUI/Classes/AnimatedImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SDWebImage
#if os(iOS) || os(tvOS) || os(macOS)

/// A coordinator object used for `AnimatedImage`native view bridge for UIKit/AppKit/WatchKit.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public final class AnimatedImageCoordinator: NSObject {

/// Any user-provided object for actual coordinator, such as delegate method, taget-action
Expand All @@ -22,6 +23,7 @@ public final class AnimatedImageCoordinator: NSObject {
}

/// 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 AnimatedImageModel : ObservableObject {
/// URL image
@Published var url: URL?
Expand All @@ -36,6 +38,7 @@ final class AnimatedImageModel : ObservableObject {
}

/// Completion Handler Binding Object, supports dynamic @State changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final class AnimatedImageHandler: ObservableObject {
// Completion Handler
@Published var successBlock: ((PlatformImage, SDImageCacheType) -> Void)?
Expand All @@ -47,6 +50,7 @@ final class AnimatedImageHandler: ObservableObject {
}

/// Layout Binding Object, supports dynamic @State changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final class AnimatedImageLayout : ObservableObject {
var contentMode: ContentMode?
var aspectRatio: CGFloat?
Expand All @@ -58,6 +62,7 @@ final class AnimatedImageLayout : ObservableObject {
}

/// Configuration Binding Object, supports dynamic @State changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final class AnimatedImageConfiguration: ObservableObject {
var incrementalLoad: Bool?
var maxBufferSize: UInt?
Expand All @@ -73,6 +78,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 {
@ObservedObject var imageModel = AnimatedImageModel()
@ObservedObject var imageHandler = AnimatedImageHandler()
Expand Down Expand Up @@ -444,6 +450,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
}

// Layout
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {

/// Configurate this view's image with the specified cap insets and options.
Expand Down Expand Up @@ -483,6 +490,7 @@ extension AnimatedImage {
}

// Aspect Ratio
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {
/// Constrains this view's dimensions to the specified aspect ratio.
/// - Parameters:
Expand Down Expand Up @@ -541,6 +549,7 @@ extension AnimatedImage {
}

// AnimatedImage Modifier
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {

/// Total loop count for animated image rendering. Defaults to nil.
Expand Down Expand Up @@ -610,6 +619,7 @@ extension AnimatedImage {
}

// Completion Handler
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {

/// Provide the action when image load fails.
Expand Down Expand Up @@ -641,6 +651,7 @@ extension AnimatedImage {
}

// View Coordinator Handler
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {

/// Provide the action when view representable create the native view.
Expand Down Expand Up @@ -668,6 +679,7 @@ extension AnimatedImage {
}

// Web Image convenience
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension AnimatedImage {

/// Associate a placeholder when loading image with url
Expand Down Expand Up @@ -695,6 +707,7 @@ extension AnimatedImage {
}

#if DEBUG
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
struct AnimatedImage_Previews : PreviewProvider {
static var previews: some View {
Group {
Expand Down
1 change: 1 addition & 0 deletions SDWebImageSwiftUI/Classes/ImageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import SwiftUI
import SDWebImage

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
class ImageManager : ObservableObject {
@Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image
@Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding
Expand Down
17 changes: 11 additions & 6 deletions SDWebImageSwiftUI/Classes/ImageViewWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SDWebImage
#if os(iOS) || os(tvOS) || os(macOS)

/// Use wrapper to solve tne `UIImageView`/`NSImageView` frame size become image size issue (SwiftUI's Bug)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public class AnimatedImageViewWrapper : PlatformView {
var wrapped = SDAnimatedImageView()
var interpolationQuality = CGInterpolationQuality.default
Expand Down Expand Up @@ -67,29 +68,33 @@ public class AnimatedImageViewWrapper : PlatformView {
}
}

private var sd_imageNameKey: Void?
private var sd_imageDataKey: Void?

/// Store the Animated Image loading state, to avoid re-query duinrg `updateView(_:)` until Source of Truth changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension PlatformView {
static private var sd_imageNameKey: Void?
static private var sd_imageDataKey: Void?

var sd_imageName: String? {
get {
objc_getAssociatedObject(self, &sd_imageNameKey) as? String
objc_getAssociatedObject(self, &PlatformView.sd_imageNameKey) as? String
}
set {
objc_setAssociatedObject(self, &sd_imageNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(self, &PlatformView.sd_imageNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var sd_imageData: Data? {
get {
objc_getAssociatedObject(self, &sd_imageDataKey) as? Data
objc_getAssociatedObject(self, &PlatformView.sd_imageDataKey) as? Data
}
set {
objc_setAssociatedObject(self, &sd_imageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(self, &PlatformView.sd_imageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

/// Use wrapper to solve the `UIProgressView`/`NSProgressIndicator` frame origin NaN crash (SwiftUI's bug)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public class ProgressIndicatorWrapper : PlatformView {
#if os(macOS)
var wrapped = NSProgressIndicator()
Expand Down
2 changes: 2 additions & 0 deletions SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI

#if os(macOS) || os(iOS) || os(tvOS)
/// An activity indicator (system style)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ActivityIndicator: PlatformViewRepresentable {
@Binding var isAnimating: Bool
var style: Style
Expand Down Expand Up @@ -71,6 +72,7 @@ public struct ActivityIndicator: PlatformViewRepresentable {
#endif
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ActivityIndicator {
public enum Style {
case medium
Expand Down
4 changes: 4 additions & 0 deletions SDWebImageSwiftUI/Classes/Indicator/Indicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
import SwiftUI

/// A type to build the indicator
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct Indicator<T> where T : View {
var content: (Binding<Bool>, Binding<CGFloat>) -> T

Expand All @@ -26,6 +27,7 @@ public struct Indicator<T> where T : View {
/// A implementation detail View Modifier with indicator
/// SwiftUI View Modifier construced by using a internal View type which modify the `body`
/// It use type system to represent the view hierarchy, and Swift `some View` syntax to hide the type detail for users
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
struct IndicatorViewModifier<T> : ViewModifier where T : View {
@ObservedObject var imageManager: ImageManager

Expand All @@ -44,6 +46,7 @@ struct IndicatorViewModifier<T> : ViewModifier where T : View {
}

#if os(macOS) || os(iOS) || os(tvOS)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Indicator where T == ActivityIndicator {
/// Activity Indicator
public static var activity: Indicator {
Expand All @@ -61,6 +64,7 @@ extension Indicator where T == ActivityIndicator {
}
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Indicator where T == ProgressIndicator {
/// Progress Indicator
public static var progress: Indicator {
Expand Down
2 changes: 2 additions & 0 deletions SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI

#if os(macOS) || os(iOS) || os(tvOS)
/// A progress bar indicator (system style)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ProgressIndicator: PlatformViewRepresentable {
@Binding var isAnimating: Bool
@Binding var progress: CGFloat
Expand Down Expand Up @@ -101,6 +102,7 @@ public struct ProgressIndicator: PlatformViewRepresentable {
#endif
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ProgressIndicator {
public enum Style {
case `default`
Expand Down
Loading