Skip to content

Commit 5b3a3fa

Browse files
committed
Add image loading category for SVGImageView subclass, add support for contentMode outside SVGKit
1 parent d7adc57 commit 5b3a3fa

File tree

10 files changed

+596
-10
lines changed

10 files changed

+596
-10
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>BuildSystemType</key>
6+
<string>Original</string>
7+
</dict>
8+
</plist>

Example/SDWebImageSVGCoder/SDViewController.m

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "SDViewController.h"
1010
#import <SDWebImageSVGCoder/SDWebImageSVGCoder.h>
11+
#import <SVGKit/SVGKit.h>
1112

1213
@interface SDViewController ()
1314

@@ -24,21 +25,40 @@ - (void)viewDidLoad
2425
[[SDImageCodersManager sharedManager] addCoder:SVGCoder];
2526
NSURL *svgURL = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/1/14/Mahuri.svg"];
2627
NSURL *svgURL2 = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/2/2d/Sample_SVG_file%2C_signature.svg"];
28+
NSURL *svgURL3 = [NSURL URLWithString:@"https://simpleicons.org/icons/github.svg"];
2729

2830
CGSize screenSize = [UIScreen mainScreen].bounds.size;
2931

30-
UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height / 2)];
31-
UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(0, screenSize.height / 2, screenSize.width, screenSize.height / 2)];
32+
// `SVGKLayeredImageView`, best on performance and do actually vector image rendering (translate SVG to CALayer tree).
33+
SVGKImageView *imageView1 = [[SVGKLayeredImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height / 2)];
34+
imageView1.sd_adjustContentMode = YES; // make `contentMode` works
35+
imageView1.contentMode = UIViewContentModeScaleAspectFill;
36+
imageView1.clipsToBounds = YES;
37+
38+
// `SVGKFastImageView`, draw SVG as bitmap dynamically when size changed.
39+
SVGKImageView *imageView2 = [[SVGKFastImageView alloc] initWithFrame:CGRectMake(0, screenSize.height / 2, screenSize.width, screenSize.height / 2)];
40+
imageView2.sd_adjustContentMode = YES;
41+
imageView2.clipsToBounds = YES;
42+
43+
// `UIImageView`, draw SVG as bitmap image with fixed size, like PNG.
44+
UIImageView *imageView3 = [[UIImageView alloc] initWithFrame:CGRectMake(screenSize.width - 100, screenSize.height - 100, 100, 100)];
3245

3346
[self.view addSubview:imageView1];
3447
[self.view addSubview:imageView2];
48+
[self.view addSubview:imageView3];
3549

3650
[imageView1 sd_setImageWithURL:svgURL placeholderImage:nil options:SDWebImageRetryFailed completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
3751
if (image) {
38-
NSLog(@"UIImageView SVG load success");
52+
NSLog(@"SVGKLayeredImageView SVG load success");
3953
}
4054
}];
4155
[imageView2 sd_setImageWithURL:svgURL2 placeholderImage:nil options:SDWebImageRetryFailed completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
56+
if (image) {
57+
NSLog(@"SVGKFastImageView SVG load success");
58+
}
59+
}];
60+
// For `UIImageView`, you can specify a desired SVG size instead of original SVG viewport (which may be small)
61+
[imageView3 sd_setImageWithURL:svgURL3 placeholderImage:nil options:SDWebImageRetryFailed context:@{SDWebImageContextVectorImageSize : @(CGSizeMake(100, 100))} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
4262
if (image) {
4363
NSLog(@"UIImageView SVG load success");
4464
}

SDWebImageSVGCoder/Classes/SDImageSVGCoder.m

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
//
77

88
#import "SDImageSVGCoder.h"
9+
#import "SDWebImageSVGCoderDefine.h"
910
#import <SVGKit/SVGKit.h>
1011

11-
#define kXMLTagStart @"<?xml"
1212
#define kSVGTagEnd @"</svg>"
1313

1414
@implementation SDImageSVGCoder
@@ -38,6 +38,15 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
3838
return nil;
3939
}
4040

41+
// Check specified image size
42+
SDWebImageContext *context = options[SDImageCoderWebImageContext];
43+
if (context[SDWebImageContextVectorImageSize]) {
44+
CGSize imageSize = [context[SDWebImageContextVectorImageSize] CGSizeValue];
45+
if (!CGSizeEqualToSize(imageSize, CGSizeZero)) {
46+
svgImage.size = imageSize;
47+
}
48+
}
49+
4150
UIImage *image = svgImage.UIImage;
4251
if (!image) {
4352
return nil;
@@ -68,13 +77,8 @@ + (BOOL)isSVGFormatForData:(NSData *)data {
6877
if (data.length <= 100) {
6978
return NO;
7079
}
71-
// Check start with XML tag
72-
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 100)] encoding:NSASCIIStringEncoding];
73-
if (![testString containsString:kXMLTagStart]) {
74-
return NO;
75-
}
7680
// Check end with SVG tag
77-
testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
81+
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
7882
if (![testString containsString:kSVGTagEnd]) {
7983
return NO;
8084
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// SDSVGImage.h
3+
// SDWebImageSVGCoder
4+
//
5+
// Created by lizhuoli on 2018/10/10.
6+
//
7+
8+
#import <SDWebImage/SDWebImage.h>
9+
#import <SVGKit/SVGKit.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface SDSVGImage : UIImage <SDAnimatedImage>
14+
15+
@property (nonatomic, strong, nullable, readonly) SVGKImage *SVGImage;
16+
17+
/**
18+
Create the wrapper with specify `SVGKImage` instance. The instance should be nonnull.
19+
This is a convenience method for some use cases, for example, create a placeholder with `SVGKImage`.
20+
21+
@param image The `SVGKImage` instance
22+
@return An initialized object
23+
*/
24+
- (nonnull instancetype)initWithSVGImage:(nonnull SVGKImage *)image;
25+
26+
// This class override these methods from UIImage
27+
// You should use these methods to create a new SVG image. Use other methods just call super instead.
28+
+ (nullable instancetype)imageWithContentsOfFile:(nonnull NSString *)path;
29+
+ (nullable instancetype)imageWithData:(nonnull NSData *)data;
30+
+ (nullable instancetype)imageWithData:(nonnull NSData *)data scale:(CGFloat)scale;
31+
- (nullable instancetype)initWithContentsOfFile:(nonnull NSString *)path;
32+
- (nullable instancetype)initWithData:(nonnull NSData *)data;
33+
- (nullable instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale;
34+
35+
@end
36+
37+
NS_ASSUME_NONNULL_END
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// SDSVGImage.m
3+
// SDWebImageSVGCoder
4+
//
5+
// Created by lizhuoli on 2018/10/10.
6+
//
7+
8+
#import "SDSVGImage.h"
9+
10+
@interface SDSVGImage ()
11+
12+
@property (nonatomic, strong, nullable) SVGKImage *SVGImage;
13+
14+
@end
15+
16+
@implementation SDSVGImage
17+
18+
- (instancetype)initWithSVGImage:(SVGKImage *)image {
19+
NSParameterAssert(image);
20+
UIImage *posterImage = image.UIImage;
21+
self = [super initWithCGImage:posterImage.CGImage scale:posterImage.scale orientation:posterImage.imageOrientation];
22+
if (self) {
23+
self.SVGImage = image;
24+
}
25+
return self;
26+
}
27+
28+
+ (instancetype)imageWithContentsOfFile:(NSString *)path {
29+
return [[self alloc] initWithContentsOfFile:path];
30+
}
31+
32+
+ (instancetype)imageWithData:(NSData *)data {
33+
return [[self alloc] initWithData:data];
34+
}
35+
36+
+ (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
37+
return [[self alloc] initWithData:data scale:scale];
38+
}
39+
40+
- (instancetype)initWithData:(NSData *)data {
41+
return [self initWithData:data scale:1];
42+
}
43+
44+
- (instancetype)initWithContentsOfFile:(NSString *)path {
45+
NSData *data = [NSData dataWithContentsOfFile:path];
46+
return [self initWithData:data];
47+
}
48+
49+
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
50+
return [self initWithData:data scale:scale options:nil];
51+
}
52+
53+
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
54+
SVGKImage *svgImage = [[SVGKImage alloc] initWithData:data];
55+
if (!svgImage) {
56+
return nil;
57+
}
58+
return [self initWithSVGImage:svgImage];
59+
}
60+
61+
- (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
62+
// Does not support progressive load for GIF images at all
63+
return nil;
64+
}
65+
66+
#pragma mark - SDAnimatedImageProvider
67+
68+
- (nullable NSData *)animatedImageData {
69+
return nil;
70+
}
71+
72+
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
73+
return 0;
74+
}
75+
76+
- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
77+
return nil;
78+
}
79+
80+
- (NSUInteger)animatedImageFrameCount {
81+
return 0;
82+
}
83+
84+
- (NSUInteger)animatedImageLoopCount {
85+
return 0;
86+
}
87+
88+
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// SDWebImageSVGCoderDefine.h
3+
// SDWebImageSVGCoder
4+
//
5+
// Created by lizhuoli on 2018/10/11.
6+
//
7+
8+
#import <SDWebImage/SDWebImage.h>
9+
10+
@class SVGKImage;
11+
12+
/**
13+
Adjust `SVGKImage`'s size && internal layerTree position to match the specify `contentMode` of view size.
14+
@note Though this util method can be used outside this framework. For simple SVG image loading, it's recommaned to use `sd_adjustContentMode` property on `SVGKImageView+WebCache`.
15+
16+
@param svgImage `SVGKImage` instance, should not be nil.
17+
@param contentMode The contentMode to be applied. All possible contentMode are supported.
18+
@param viewSize Target view size, typically specify the `view.bounds.size`.
19+
*/
20+
FOUNDATION_EXPORT void SDAdjustSVGContentMode(SVGKImage * __nonnull svgImage, UIViewContentMode contentMode, CGSize viewSize);
21+
22+
/**
23+
A CGSize raw value which specify the desired vector image size during image loading. Because vector image like SVG format, may not contains a fixed size, or you want to get a larger size bitmap representation UIImage. (NSValue)
24+
*/
25+
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextVectorImageSize;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// SDWebImageSVGCoderDefine.m
3+
// SDWebImageSVGCoder
4+
//
5+
// Created by lizhuoli on 2018/10/11.
6+
//
7+
8+
#import "SDWebImageSVGCoderDefine.h"
9+
#import <SVGKit/SVGKit.h>
10+
11+
void SDAdjustSVGContentMode(SVGKImage * svgImage, UIViewContentMode contentMode, CGSize viewSize) {
12+
NSCParameterAssert(svgImage);
13+
if (!svgImage.hasSize) {
14+
// `SVGKImage` does not has size, specify the content size, earily return
15+
svgImage.size = viewSize;
16+
return;
17+
}
18+
CGSize imageSize = svgImage.size;
19+
if (imageSize.height == 0 || viewSize.height == 0) {
20+
return;
21+
}
22+
CGFloat wScale = viewSize.width / imageSize.width;
23+
CGFloat hScale = viewSize.height / imageSize.height;
24+
CGFloat imageAspect = imageSize.width / imageSize.height;
25+
CGFloat viewAspect = viewSize.width / viewSize.height;
26+
CGFloat smallestScaleUp = MIN(wScale, hScale);
27+
CGFloat biggestScaleDown = MAX(wScale, hScale);
28+
CGFloat xPosition;
29+
CGFloat yPosition;
30+
31+
// Geometry calculation
32+
switch (contentMode) {
33+
case UIViewContentModeScaleToFill: {
34+
svgImage.size = viewSize;
35+
}
36+
break;
37+
case UIViewContentModeScaleAspectFit: {
38+
CGFloat scale = smallestScaleUp < 1.0f ? smallestScaleUp : biggestScaleDown;
39+
CGSize targetSize = CGSizeApplyAffineTransform(imageSize, CGAffineTransformMakeScale(scale, scale));
40+
CGFloat x = ceil(viewSize.width - targetSize.width) / 2;
41+
CGFloat y = ceil(viewSize.height - targetSize.height) / 2;
42+
svgImage.size = targetSize;
43+
svgImage.DOMTree.viewport = SVGRectMake(x, y, targetSize.width, targetSize.height);
44+
// masksToBounds to clip the sublayer which beyond the viewport to match `UIImageView` behavior
45+
svgImage.CALayerTree.masksToBounds = YES;
46+
}
47+
break;
48+
case UIViewContentModeScaleAspectFill: {
49+
CGFloat scale;
50+
if (imageAspect < viewAspect) {
51+
// scale width
52+
scale = wScale;
53+
} else {
54+
// scale height
55+
scale = hScale;
56+
}
57+
CGSize targetSize = CGSizeApplyAffineTransform(imageSize, CGAffineTransformMakeScale(scale, scale));
58+
svgImage.size = targetSize;
59+
if (imageAspect < viewAspect) {
60+
// need center y as well
61+
xPosition = targetSize.width / 2;
62+
yPosition = viewSize.height / 2;
63+
} else {
64+
// need center x as well
65+
xPosition = viewSize.width / 2;
66+
yPosition = targetSize.height / 2;
67+
}
68+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
69+
}
70+
break;
71+
case UIViewContentModeTop: {
72+
xPosition = viewSize.width / 2;
73+
yPosition = imageSize.height / 2;
74+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
75+
}
76+
break;
77+
case UIViewContentModeTopLeft: {
78+
xPosition = imageSize.width / 2;
79+
yPosition = imageSize.height / 2;
80+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
81+
}
82+
break;
83+
case UIViewContentModeTopRight: {
84+
xPosition = -imageSize.width / 2 + viewSize.width;
85+
yPosition = imageSize.height / 2;
86+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
87+
}
88+
break;
89+
case UIViewContentModeCenter: {
90+
xPosition = viewSize.width / 2;
91+
yPosition = viewSize.height / 2;
92+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
93+
}
94+
break;
95+
case UIViewContentModeLeft: {
96+
xPosition = imageSize.width / 2;
97+
yPosition = viewSize.height / 2;
98+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
99+
}
100+
break;
101+
case UIViewContentModeRight: {
102+
xPosition = -imageSize.width / 2 + viewSize.width;
103+
yPosition = viewSize.height / 2;
104+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
105+
}
106+
break;
107+
case UIViewContentModeBottom: {
108+
xPosition = viewSize.width / 2;
109+
yPosition = -imageSize.height / 2 + viewSize.height;
110+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
111+
}
112+
break;
113+
case UIViewContentModeBottomLeft: {
114+
xPosition = imageSize.width / 2;
115+
yPosition = -imageSize.height / 2 + viewSize.height;
116+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
117+
}
118+
break;
119+
case UIViewContentModeBottomRight: {
120+
xPosition = -imageSize.width / 2 + viewSize.width;
121+
yPosition = -imageSize.height / 2 + viewSize.height;
122+
svgImage.CALayerTree.position = CGPointMake(xPosition, yPosition);
123+
}
124+
break;
125+
case UIViewContentModeRedraw: {
126+
svgImage.CALayerTree.needsDisplayOnBoundsChange = YES;
127+
}
128+
break;
129+
}
130+
}
131+
132+
SDWebImageContextOption _Nonnull const SDWebImageContextVectorImageSize = @"vectorImageSize";

0 commit comments

Comments
 (0)