Add the ability for ASNetworkImageNodes to keep track of their progressive image quality

This commit is contained in:
Wendy
2016-04-21 18:57:53 -07:00
parent f7985d2d26
commit 0b55df9649
8 changed files with 109 additions and 27 deletions

View File

@@ -337,6 +337,10 @@
9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; };
9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; };
9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; };
A2763D791CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; };
A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; };
A2763D7B1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */; };
A2763D7C1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */; };
A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Public, ); }; };
A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -808,6 +812,8 @@
9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = "<group>"; };
9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = "<group>"; };
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASPINRemoteImageDownloader.h; path = Details/ASPINRemoteImageDownloader.h; sourceTree = "<group>"; };
A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASPINRemoteImageDownloader.m; path = Details/ASPINRemoteImageDownloader.m; sourceTree = "<group>"; };
A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = "<group>"; };
A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = "<group>"; };
AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = "<group>"; };
@@ -1064,6 +1070,8 @@
055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */,
25E327541C16819500A2170C /* ASPagerNode.h */,
25E327551C16819500A2170C /* ASPagerNode.m */,
A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */,
A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */,
D785F6601A74327E00291744 /* ASScrollNode.h */,
D785F6611A74327E00291744 /* ASScrollNode.m */,
B0F880581BEAEC7500D17647 /* ASTableNode.h */,
@@ -1461,6 +1469,7 @@
058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */,
7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */,
68B8A4DC1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h in Headers */,
A2763D791CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */,
058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */,
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */,
B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */,
@@ -1620,6 +1629,7 @@
B35062581B010F070018CF92 /* ASAvailability.h in Headers */,
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */,
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */,
A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */,
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */,
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */,
68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */,

View File

@@ -103,4 +103,13 @@ ASDISPLAYNODE_EXTERN_C_END
*/
- (void)cancelLayoutTransitionsInProgress;
/**
* @abstract Indicates that the receiver and all subnodes have finished displaying. May be called more than once, for example if the receiver has
* a network image node. This is called after the first display pass even if network image nodes have not downloaded anything (text would be done,
* and other nodes that are ready to do their final display). Each render of every progressive jpeg network node would cause this to be called, so
* this hook could be called up to 1 + (pJPEGcount * pJPEGrenderCount) times. The render count depends on how many times the downloader calls the
* progressImage block.
*/
- (void)hierarchyDisplayDidFinish;
@end

View File

@@ -1722,19 +1722,23 @@ static NSInteger incrementIfFound(NSInteger i) {
[_pendingDisplayNodes removeObject:node];
if (_pendingDisplayNodes.count == 0 && _placeholderLayer.superlayer && ![self placeholderShouldPersist]) {
void (^cleanupBlock)() = ^{
[_placeholderLayer removeFromSuperlayer];
};
if (_pendingDisplayNodes.count == 0) {
[self hierarchyDisplayDidFinish];
if (_placeholderLayer.superlayer && ![self placeholderShouldPersist]) {
void (^cleanupBlock)() = ^{
[_placeholderLayer removeFromSuperlayer];
};
if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
[CATransaction begin];
[CATransaction setCompletionBlock:cleanupBlock];
[CATransaction setAnimationDuration:_placeholderFadeDuration];
_placeholderLayer.opacity = 0.0;
[CATransaction commit];
} else {
cleanupBlock();
if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
[CATransaction begin];
[CATransaction setCompletionBlock:cleanupBlock];
[CATransaction setAnimationDuration:_placeholderFadeDuration];
_placeholderLayer.opacity = 0.0;
[CATransaction commit];
} else {
cleanupBlock();
}
}
}
}
@@ -2390,6 +2394,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
});
}
- (void)hierarchyDisplayDidFinish
{
// subclass hook
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// subclass hook

View File

@@ -469,7 +469,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
ASImageDownloaderProgressImage progress = nil;
if (ASInterfaceStateIncludesVisible(interfaceState)) {
__weak __typeof__(self) weakSelf = self;
progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) {
progress = ^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;

View File

@@ -73,6 +73,17 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign, readwrite) BOOL shouldCacheImage;
/**
* The image quality of the current image. This is a number between 0 and 1 and can be used to track
* progressive progress. Calculated by dividing number of bytes / expected number of total bytes.
*/
@property (nonatomic, assign, readonly) CGFloat currentImageQuality;
/**
* The image quality (value between 0 and 1) of the last image that completed displaying.
*/
@property (nonatomic, assign, readonly) CGFloat renderedImageQuality;
@end

View File

@@ -39,17 +39,19 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
id _downloadIdentifier;
BOOL _imageLoaded;
CGFloat _currentImageQuality;
CGFloat _renderedImageQuality;
BOOL _delegateSupportsDidStartFetchingData;
BOOL _delegateSupportsDidFailWithError;
BOOL _delegateSupportsImageNodeDidFinishDecoding;
//set on init only
BOOL _downloaderSupportsNewProtocol;
BOOL _downloaderImplementsSetProgress;
BOOL _downloaderImplementsSetPriority;
BOOL _downloaderImplementsAnimatedImage;
BOOL _cacheSupportsNewProtocol;
BOOL _cacheSupportsClearing;
BOOL _cacheSupportsSynchronousFetch;
@@ -120,9 +122,13 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_URL = URL;
if (reset || _URL == nil)
if (reset || _URL == nil) {
self.image = _defaultImage;
ASPerformBlockOnMainThread(^{
_currentImageQuality = 0.0;
});
}
if (self.interfaceState & ASInterfaceStateFetchData) {
[self fetchData];
}
@@ -145,6 +151,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_defaultImage = defaultImage;
if (!_imageLoaded) {
ASPerformBlockOnMainThread(^{
_currentImageQuality = 0.0;
});
_lock.unlock();
// Locking: it is important to release _lock before entering setImage:, as it needs to release the lock before -invalidateCalculatedLayout.
// If we continue to hold the lock here, it will still be locked until the next unlock() call, causing a possible deadlock with
@@ -161,6 +170,30 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
return _defaultImage;
}
- (void)setCurrentImageQuality:(CGFloat)currentImageQuality
{
ASDN::MutexLocker l(_lock);
_currentImageQuality = currentImageQuality;
}
- (CGFloat)currentImageQuality
{
ASDN::MutexLocker l(_lock);
return _currentImageQuality;
}
- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality
{
ASDN::MutexLocker l(_lock);
_renderedImageQuality = renderedImageQuality;
}
- (CGFloat)renderedImageQuality
{
ASDN::MutexLocker l(_lock);
return _renderedImageQuality;
}
- (void)setDelegate:(id<ASNetworkImageNodeDelegate>)delegate
{
ASDN::MutexLocker l(_lock);
@@ -196,6 +229,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (result) {
self.image = result;
_imageLoaded = YES;
_currentImageQuality = 1.0;
}
}
}
@@ -276,7 +310,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
ASImageDownloaderProgressImage progress = nil;
if (ASInterfaceStateIncludesVisible(interfaceState)) {
__weak __typeof__(self) weakSelf = self;
progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) {
progress = ^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
@@ -288,6 +322,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
return;
}
strongSelf.image = progressImage;
ASPerformBlockOnMainThread(^{
strongSelf->_currentImageQuality = progress;
});
};
}
[_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
@@ -310,6 +347,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
self.animatedImage = nil;
self.image = _defaultImage;
_imageLoaded = NO;
ASPerformBlockOnMainThread(^{
_currentImageQuality = 0.0;
});
}
- (void)_cancelImageDownload
@@ -393,6 +433,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
}
_imageLoaded = YES;
self.currentImageQuality = 1.0;
[_delegate imageNode:self didLoadImage:self.image];
});
}
@@ -418,6 +459,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
} else {
strongSelf.image = [imageContainer asdk_image];
}
strongSelf->_currentImageQuality = 1.0;
}
strongSelf->_downloadIdentifier = nil;
@@ -472,13 +514,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
#pragma mark - ASDisplayNode+Subclasses
- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange
- (void)displayDidFinish
{
if (self.asyncdisplaykit_asyncTransactionContainerState == ASAsyncTransactionContainerStateNoTransactions) {
ASDN::MutexLocker l(_lock);
if (self.layer.contents != nil && _delegateSupportsImageNodeDidFinishDecoding) {
[self.delegate imageNodeDidFinishDecoding:self];
}
[super displayDidFinish];
ASDN::MutexLocker l(_lock);
if (_delegateSupportsImageNodeDidFinishDecoding && self.layer.contents != nil) {
_renderedImageQuality = _currentImageQuality;
[self.delegate imageNodeDidFinishDecoding:self];
}
}

View File

@@ -63,7 +63,7 @@ typedef void(^ASImageCacherCompletion)(id <ASImageContainerProtocol> _Nullable i
typedef void(^ASImageDownloaderCompletion)(id <ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier);
typedef void(^ASImageDownloaderProgress)(CGFloat progress);
typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, id _Nullable downloadIdentifier);
typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, CGFloat progress, id _Nullable downloadIdentifier);
typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) {
ASImageDownloaderPriorityPreload = 0,

View File

@@ -174,7 +174,7 @@
if (progressBlock) {
[[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) {
dispatch_async(callbackQueue, ^{
progressBlock(result.image, result.UUID);
progressBlock(result.image, result.renderedImageQuality, result.UUID);
});
} ofTaskWithUUID:downloadIdentifier];
} else {