Commit 07fd87d3 by SunARcTech2

changes

parent 3c37240a
No preview for this file type
...@@ -141,13 +141,13 @@ ...@@ -141,13 +141,13 @@
B0F5F58F2140E6C60071443A /* Certificates.p12 in Resources */ = {isa = PBXBuildFile; fileRef = B0F5F5862140E6C50071443A /* Certificates.p12 */; }; B0F5F58F2140E6C60071443A /* Certificates.p12 in Resources */ = {isa = PBXBuildFile; fileRef = B0F5F5862140E6C50071443A /* Certificates.p12 */; };
B14F485320C6B1C400C95FB0 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F484A20C6B1C400C95FB0 /* CardView.swift */; }; B14F485320C6B1C400C95FB0 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F484A20C6B1C400C95FB0 /* CardView.swift */; };
B1A3E6BE20E0E9E8003194B1 /* DrawButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A3E6B520E0E9E8003194B1 /* DrawButton.swift */; }; B1A3E6BE20E0E9E8003194B1 /* DrawButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A3E6B520E0E9E8003194B1 /* DrawButton.swift */; };
C70B7A172174962B0022781B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C70B7A162174962B0022781B /* GoogleService-Info.plist */; };
C725BD1221525FFD00D65D41 /* OrderDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1121525FFD00D65D41 /* OrderDetails.swift */; }; C725BD1221525FFD00D65D41 /* OrderDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1121525FFD00D65D41 /* OrderDetails.swift */; };
C725BD1C2152600D00D65D41 /* OrderDetailsExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1B2152600D00D65D41 /* OrderDetailsExt.swift */; }; C725BD1C2152600D00D65D41 /* OrderDetailsExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1B2152600D00D65D41 /* OrderDetailsExt.swift */; };
C725BD2021526CF700D65D41 /* FAQDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1F21526CF700D65D41 /* FAQDetailsViewController.swift */; }; C725BD2021526CF700D65D41 /* FAQDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C725BD1F21526CF700D65D41 /* FAQDetailsViewController.swift */; };
C7330EB8216DCE4000DF238B /* FormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7330EB7216DCE4000DF238B /* FormViewController.swift */; }; C7330EB8216DCE4000DF238B /* FormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7330EB7216DCE4000DF238B /* FormViewController.swift */; };
C7C33EC0215A2C69001D5B25 /* PostInFullScreenView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7C33EBF215A2C69001D5B25 /* PostInFullScreenView.xib */; }; C7C33EC0215A2C69001D5B25 /* PostInFullScreenView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7C33EBF215A2C69001D5B25 /* PostInFullScreenView.xib */; };
C7C33EC2215A2C75001D5B25 /* PostInFullScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7C33EC1215A2C75001D5B25 /* PostInFullScreenView.swift */; }; C7C33EC2215A2C75001D5B25 /* PostInFullScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7C33EC1215A2C75001D5B25 /* PostInFullScreenView.swift */; };
C7E21D80216F6F79004606E8 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7E21D7E216F6F78004606E8 /* GoogleService-Info.plist */; };
C7E21D81216F6F79004606E8 /* GoogleService-Info2.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */; }; C7E21D81216F6F79004606E8 /* GoogleService-Info2.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */; };
EC04CD3D2052CC0100B425BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC04CD3C2052CC0100B425BB /* Assets.xcassets */; }; EC04CD3D2052CC0100B425BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC04CD3C2052CC0100B425BB /* Assets.xcassets */; };
EC04CD492052CC3B00B425BB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC04CD4B2052CC3B00B425BB /* Localizable.strings */; }; EC04CD492052CC3B00B425BB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC04CD4B2052CC3B00B425BB /* Localizable.strings */; };
...@@ -688,13 +688,13 @@ ...@@ -688,13 +688,13 @@
B0F5F5862140E6C50071443A /* Certificates.p12 */ = {isa = PBXFileReference; lastKnownFileType = file; path = Certificates.p12; sourceTree = "<group>"; }; B0F5F5862140E6C50071443A /* Certificates.p12 */ = {isa = PBXFileReference; lastKnownFileType = file; path = Certificates.p12; sourceTree = "<group>"; };
B14F484A20C6B1C400C95FB0 /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; }; B14F484A20C6B1C400C95FB0 /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
B1A3E6B520E0E9E8003194B1 /* DrawButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawButton.swift; sourceTree = "<group>"; }; B1A3E6B520E0E9E8003194B1 /* DrawButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawButton.swift; sourceTree = "<group>"; };
C70B7A162174962B0022781B /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
C725BD1121525FFD00D65D41 /* OrderDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetails.swift; sourceTree = "<group>"; }; C725BD1121525FFD00D65D41 /* OrderDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetails.swift; sourceTree = "<group>"; };
C725BD1B2152600D00D65D41 /* OrderDetailsExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsExt.swift; sourceTree = "<group>"; }; C725BD1B2152600D00D65D41 /* OrderDetailsExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsExt.swift; sourceTree = "<group>"; };
C725BD1F21526CF700D65D41 /* FAQDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQDetailsViewController.swift; sourceTree = "<group>"; }; C725BD1F21526CF700D65D41 /* FAQDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQDetailsViewController.swift; sourceTree = "<group>"; };
C7330EB7216DCE4000DF238B /* FormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormViewController.swift; sourceTree = "<group>"; }; C7330EB7216DCE4000DF238B /* FormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormViewController.swift; sourceTree = "<group>"; };
C7C33EBF215A2C69001D5B25 /* PostInFullScreenView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PostInFullScreenView.xib; sourceTree = "<group>"; }; C7C33EBF215A2C69001D5B25 /* PostInFullScreenView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PostInFullScreenView.xib; sourceTree = "<group>"; };
C7C33EC1215A2C75001D5B25 /* PostInFullScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInFullScreenView.swift; sourceTree = "<group>"; }; C7C33EC1215A2C75001D5B25 /* PostInFullScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInFullScreenView.swift; sourceTree = "<group>"; };
C7E21D7E216F6F78004606E8 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info2.plist"; sourceTree = "<group>"; }; C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info2.plist"; sourceTree = "<group>"; };
EC04CD322052CC0100B425BB /* Bhagyashree.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bhagyashree.app; sourceTree = BUILT_PRODUCTS_DIR; }; EC04CD322052CC0100B425BB /* Bhagyashree.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bhagyashree.app; sourceTree = BUILT_PRODUCTS_DIR; };
EC04CD3C2052CC0100B425BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; EC04CD3C2052CC0100B425BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
...@@ -1488,8 +1488,8 @@ ...@@ -1488,8 +1488,8 @@
B0B468F320B52A9C0076B7BE /* Sunarc */, B0B468F320B52A9C0076B7BE /* Sunarc */,
EC04CD3C2052CC0100B425BB /* Assets.xcassets */, EC04CD3C2052CC0100B425BB /* Assets.xcassets */,
B024385020B52EF5007D12AE /* Fengshui.xcassets */, B024385020B52EF5007D12AE /* Fengshui.xcassets */,
C7E21D7E216F6F78004606E8 /* GoogleService-Info.plist */,
C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */, C7E21D7F216F6F79004606E8 /* GoogleService-Info2.plist */,
C70B7A162174962B0022781B /* GoogleService-Info.plist */,
EC04CD412052CC0100B425BB /* Info.plist */, EC04CD412052CC0100B425BB /* Info.plist */,
); );
path = Bhagyashree; path = Bhagyashree;
...@@ -2234,7 +2234,6 @@ ...@@ -2234,7 +2234,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C7C33EC0215A2C69001D5B25 /* PostInFullScreenView.xib in Resources */, C7C33EC0215A2C69001D5B25 /* PostInFullScreenView.xib in Resources */,
C7E21D80216F6F79004606E8 /* GoogleService-Info.plist in Resources */,
EC04CFE32052CF3900B425BB /* Main.storyboard in Resources */, EC04CFE32052CF3900B425BB /* Main.storyboard in Resources */,
EC04CEC02052CE4A00B425BB /* CartTableViewCell.xib in Resources */, EC04CEC02052CE4A00B425BB /* CartTableViewCell.xib in Resources */,
EC04CF4D2052CF0B00B425BB /* TopCategoryTableViewCell.xib in Resources */, EC04CF4D2052CF0B00B425BB /* TopCategoryTableViewCell.xib in Resources */,
...@@ -2321,6 +2320,7 @@ ...@@ -2321,6 +2320,7 @@
EC04CEF82052CE9400B425BB /* AddreessViewCell2.xib in Resources */, EC04CEF82052CE9400B425BB /* AddreessViewCell2.xib in Resources */,
EC04CF542052CF0B00B425BB /* HotdealsTableViewCell.xib in Resources */, EC04CF542052CF0B00B425BB /* HotdealsTableViewCell.xib in Resources */,
EC04D0322052D2F400B425BB /* ContinueToBillTableViewCell.xib in Resources */, EC04D0322052D2F400B425BB /* ContinueToBillTableViewCell.xib in Resources */,
C70B7A172174962B0022781B /* GoogleService-Info.plist in Resources */,
B024385120B52EF5007D12AE /* Fengshui.xcassets in Resources */, B024385120B52EF5007D12AE /* Fengshui.xcassets in Resources */,
EC04CFC02052CF2A00B425BB /* IQKeyboardManager.bundle in Resources */, EC04CFC02052CF2A00B425BB /* IQKeyboardManager.bundle in Resources */,
EC04CE182052CDA300B425BB /* TopSellingProduct.xib in Resources */, EC04CE182052CDA300B425BB /* TopSellingProduct.xib in Resources */,
...@@ -2918,16 +2918,16 @@ ...@@ -2918,16 +2918,16 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Bhagyashree/Bhagyashree.entitlements; CODE_SIGN_ENTITLEMENTS = Bhagyashree/Bhagyashree.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = AY4776L6L2; DEVELOPMENT_TEAM = AY4776L6L2;
INFOPLIST_FILE = Bhagyashree/Info.plist; INFOPLIST_FILE = Bhagyashree/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.gdi.yuanzhongsiuseller; PRODUCT_BUNDLE_IDENTIFIER = com.gdi.yuanzhongsiumaster;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "dd4d6db4-b04b-4852-bc63-57c403be8da1"; PROVISIONING_PROFILE = "14cf4f94-331e-40f4-8735-1b68dfa7667f";
PROVISIONING_PROFILE_SPECIFIER = Seller_Dev; PROVISIONING_PROFILE_SPECIFIER = Master_Production;
SWIFT_OBJC_BRIDGING_HEADER = "Bhagyashree/MARKET PLACE/Seller Create Credit Memo/Bhagyashree-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Bhagyashree/MARKET PLACE/Seller Create Credit Memo/Bhagyashree-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
...@@ -2943,16 +2943,16 @@ ...@@ -2943,16 +2943,16 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Bhagyashree/Bhagyashree.entitlements; CODE_SIGN_ENTITLEMENTS = Bhagyashree/Bhagyashree.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = AY4776L6L2; DEVELOPMENT_TEAM = AY4776L6L2;
INFOPLIST_FILE = Bhagyashree/Info.plist; INFOPLIST_FILE = Bhagyashree/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.gdi.yuanzhongsiuseller; PRODUCT_BUNDLE_IDENTIFIER = com.gdi.yuanzhongsiumaster;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "dd4d6db4-b04b-4852-bc63-57c403be8da1"; PROVISIONING_PROFILE = "14cf4f94-331e-40f4-8735-1b68dfa7667f";
PROVISIONING_PROFILE_SPECIFIER = Seller_Dev; PROVISIONING_PROFILE_SPECIFIER = Master_Production;
SWIFT_OBJC_BRIDGING_HEADER = "Bhagyashree/MARKET PLACE/Seller Create Credit Memo/Bhagyashree-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Bhagyashree/MARKET PLACE/Seller Create Credit Memo/Bhagyashree-Bridging-Header.h";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<key>Bhagyashree.xcscheme</key> <key>Bhagyashree.xcscheme</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>34</integer> <integer>35</integer>
</dict> </dict>
</dict> </dict>
</dict> </dict>
......
...@@ -127,8 +127,8 @@ ...@@ -127,8 +127,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "127" startingLineNumber = "127"
endingLineNumber = "127" endingLineNumber = "127"
landmarkName = "tableView(_:didSelectRowAt:)" landmarkName = "unknown"
landmarkType = "7"> landmarkType = "0">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
...@@ -143,8 +143,8 @@ ...@@ -143,8 +143,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "124" startingLineNumber = "124"
endingLineNumber = "124" endingLineNumber = "124"
landmarkName = "tableView(_:didSelectRowAt:)" landmarkName = "unknown"
landmarkType = "7"> landmarkType = "0">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
...@@ -159,8 +159,8 @@ ...@@ -159,8 +159,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "64" startingLineNumber = "64"
endingLineNumber = "64" endingLineNumber = "64"
landmarkName = "playAudio(audioPath:success:failure:progress:)" landmarkName = "unknown"
landmarkType = "7"> landmarkType = "0">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
</Breakpoints> </Breakpoints>
......
No preview for this file type
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
<key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key> <key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key>
<string>ca-app-pub-3940256099942544/4411468910</string> <string>ca-app-pub-3940256099942544/4411468910</string>
<key>CLIENT_ID</key> <key>CLIENT_ID</key>
<string>360669556539-tr24dr2sh481mum3ijjkassug7p7l04p.apps.googleusercontent.com</string> <string>360669556539-aobitroejmk5lrne4u0mv22l41l9prv7.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key> <key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.360669556539-tr24dr2sh481mum3ijjkassug7p7l04p</string> <string>com.googleusercontent.apps.360669556539-aobitroejmk5lrne4u0mv22l41l9prv7</string>
<key>API_KEY</key> <key>API_KEY</key>
<string>AIzaSyBbcoObffadmduuSAhDozLyWbyiqg7Mf9s</string> <string>AIzaSyBbcoObffadmduuSAhDozLyWbyiqg7Mf9s</string>
<key>GCM_SENDER_ID</key> <key>GCM_SENDER_ID</key>
...@@ -17,24 +17,24 @@ ...@@ -17,24 +17,24 @@
<key>PLIST_VERSION</key> <key>PLIST_VERSION</key>
<string>1</string> <string>1</string>
<key>BUNDLE_ID</key> <key>BUNDLE_ID</key>
<string>com.gdi.yuanzhongsiuseller</string> <string>com.gdi.yuanzhongsiumaster</string>
<key>PROJECT_ID</key> <key>PROJECT_ID</key>
<string>fengshui-customer</string> <string>fengshui-customer</string>
<key>STORAGE_BUCKET</key> <key>STORAGE_BUCKET</key>
<string>fengshui-customer.appspot.com</string> <string>fengshui-customer.appspot.com</string>
<key>IS_ADS_ENABLED</key> <key>IS_ADS_ENABLED</key>
<true/> <true></true>
<key>IS_ANALYTICS_ENABLED</key> <key>IS_ANALYTICS_ENABLED</key>
<false/> <false></false>
<key>IS_APPINVITE_ENABLED</key> <key>IS_APPINVITE_ENABLED</key>
<false/> <false></false>
<key>IS_GCM_ENABLED</key> <key>IS_GCM_ENABLED</key>
<true/> <true></true>
<key>IS_SIGNIN_ENABLED</key> <key>IS_SIGNIN_ENABLED</key>
<true/> <true></true>
<key>GOOGLE_APP_ID</key> <key>GOOGLE_APP_ID</key>
<string>1:360669556539:ios:53c921650750ae37</string> <string>1:360669556539:ios:a710d90793c1d99a</string>
<key>DATABASE_URL</key> <key>DATABASE_URL</key>
<string>https://fengshui-customer.firebaseio.com</string> <string>https://fengshui-customer.firebaseio.com</string>
</dict> </dict>
</plist> </plist>
\ No newline at end of file
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>4</string> <string>2</string>
<key>Fabric</key> <key>Fabric</key>
<dict> <dict>
<key>APIKey</key> <key>APIKey</key>
......
...@@ -341,7 +341,7 @@ ...@@ -341,7 +341,7 @@
<objects> <objects>
<viewController storyboardIdentifier="FloorPlanFengshuiViewController" id="zhu-Ue-WYE" customClass="FloorPlanFengshuiViewController" customModule="Bhagyashree" customModuleProvider="target" sceneMemberID="viewController"> <viewController storyboardIdentifier="FloorPlanFengshuiViewController" id="zhu-Ue-WYE" customClass="FloorPlanFengshuiViewController" customModule="Bhagyashree" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="vOF-80-GgO"> <view key="view" contentMode="scaleToFill" id="vOF-80-GgO">
<rect key="frame" x="0.0" y="0.0" width="375" height="1000"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sXk-Cp-lQo"> <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sXk-Cp-lQo">
......
...@@ -51,12 +51,12 @@ PODS: ...@@ -51,12 +51,12 @@ PODS:
- "GoogleToolboxForMac/NSData+zlib (2.1.4)": - "GoogleToolboxForMac/NSData+zlib (2.1.4)":
- GoogleToolboxForMac/Defines (= 2.1.4) - GoogleToolboxForMac/Defines (= 2.1.4)
- GTMSessionFetcher/Core (1.1.15) - GTMSessionFetcher/Core (1.1.15)
- ImageSlideshow (1.7.0): - ImageSlideshow (1.4.1):
- ImageSlideshow/Core (= 1.7.0) - ImageSlideshow/Core (= 1.4.1)
- ImageSlideshow/Core (1.7.0) - ImageSlideshow/Core (1.4.1)
- ImageSlideshow/SDWebImage (1.7.0): - ImageSlideshow/SDWebImage (1.4.1):
- ImageSlideshow/Core - ImageSlideshow/Core
- SDWebImage (< 5.0, >= 3.7) - SDWebImage (~> 3.7)
- INSPhotoGallery (1.2.5) - INSPhotoGallery (1.2.5)
- IQKeyboardManagerSwift (5.0.8) - IQKeyboardManagerSwift (5.0.8)
- Kingfisher (4.7.0) - Kingfisher (4.7.0)
...@@ -74,9 +74,9 @@ PODS: ...@@ -74,9 +74,9 @@ PODS:
- Realm/Headers (3.11.0) - Realm/Headers (3.11.0)
- RealmSwift (3.11.0): - RealmSwift (3.11.0):
- Realm (= 3.11.0) - Realm (= 3.11.0)
- SDWebImage (4.4.2): - SDWebImage (3.8.2):
- SDWebImage/Core (= 4.4.2) - SDWebImage/Core (= 3.8.2)
- SDWebImage/Core (4.4.2) - SDWebImage/Core (3.8.2)
- SVProgressHUD (2.0.3) - SVProgressHUD (2.0.3)
- SwiftDate (4.4.1) - SwiftDate (4.4.1)
- TimeAgoInWords (3.0.0) - TimeAgoInWords (3.0.0)
...@@ -156,7 +156,7 @@ SPEC CHECKSUMS: ...@@ -156,7 +156,7 @@ SPEC CHECKSUMS:
FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2 GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2
ImageSlideshow: f8eec5e37a980f60923b585ff23b0e140fd24018 ImageSlideshow: 1bb0e610e28a71e8936d57265d882f61714a470b
INSPhotoGallery: 8bd5b434e70d06dd698085f8e865f6602d82014b INSPhotoGallery: 8bd5b434e70d06dd698085f8e865f6602d82014b
IQKeyboardManagerSwift: 2e7dc7f98c111458c1ea2b373f893e8cf95e2b97 IQKeyboardManagerSwift: 2e7dc7f98c111458c1ea2b373f893e8cf95e2b97
Kingfisher: da6b005aa96d37698e3e4f1ccfe96a5b9bbf27d6 Kingfisher: da6b005aa96d37698e3e4f1ccfe96a5b9bbf27d6
...@@ -167,7 +167,7 @@ SPEC CHECKSUMS: ...@@ -167,7 +167,7 @@ SPEC CHECKSUMS:
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc
RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681 SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c
SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4 SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4
SwiftDate: a70a534d4feed03b2a2b575e36bef82dc2286a8d SwiftDate: a70a534d4feed03b2a2b575e36bef82dc2286a8d
TimeAgoInWords: 633dbb30810de855333dedd1d5033d28b1ecfd6f TimeAgoInWords: 633dbb30810de855333dedd1d5033d28b1ecfd6f
......
...@@ -31,11 +31,11 @@ extension UIActivityIndicatorView: ActivityIndicatorView { ...@@ -31,11 +31,11 @@ extension UIActivityIndicatorView: ActivityIndicatorView {
} }
public func show() { public func show() {
startAnimating() self.startAnimating()
} }
public func hide() { public func hide() {
stopAnimating() self.stopAnimating()
} }
} }
...@@ -43,8 +43,7 @@ extension UIActivityIndicatorView: ActivityIndicatorView { ...@@ -43,8 +43,7 @@ extension UIActivityIndicatorView: ActivityIndicatorView {
@objcMembers @objcMembers
open class DefaultActivityIndicator: ActivityIndicatorFactory { open class DefaultActivityIndicator: ActivityIndicatorFactory {
/// activity indicator style /// activity indicator style
open var style: UIActivityIndicatorView.Style open var style: UIActivityIndicatorViewStyle
/// activity indicator color /// activity indicator color
open var color: UIColor? open var color: UIColor?
...@@ -52,18 +51,14 @@ open class DefaultActivityIndicator: ActivityIndicatorFactory { ...@@ -52,18 +51,14 @@ open class DefaultActivityIndicator: ActivityIndicatorFactory {
/// ///
/// - style: activity indicator style /// - style: activity indicator style
/// - color: activity indicator color /// - color: activity indicator color
public init(style: UIActivityIndicatorView.Style = .gray, color: UIColor? = nil) { public init(style: UIActivityIndicatorViewStyle = .gray, color: UIColor? = nil) {
self.style = style self.style = style
self.color = color self.color = color
} }
/// create ActivityIndicatorView instance /// create ActivityIndicatorView instance
open func create() -> ActivityIndicatorView { open func create() -> ActivityIndicatorView {
#if swift(>=4.2)
let activityIndicator = UIActivityIndicatorView(style: style)
#else
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: style) let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: style)
#endif
activityIndicator.color = color activityIndicator.color = color
activityIndicator.hidesWhenStopped = true activityIndicator.hidesWhenStopped = true
......
...@@ -14,7 +14,7 @@ open class FullScreenSlideshowViewController: UIViewController { ...@@ -14,7 +14,7 @@ open class FullScreenSlideshowViewController: UIViewController {
let slideshow = ImageSlideshow() let slideshow = ImageSlideshow()
slideshow.zoomEnabled = true slideshow.zoomEnabled = true
slideshow.contentScaleMode = UIViewContentMode.scaleAspectFit slideshow.contentScaleMode = UIViewContentMode.scaleAspectFit
slideshow.pageIndicatorPosition = PageIndicatorPosition(horizontal: .center, vertical: .bottom) slideshow.pageControlPosition = PageControlPosition.insideScrollView
// turns off the timer // turns off the timer
slideshow.slideshowInterval = 0 slideshow.slideshowInterval = 0
slideshow.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight] slideshow.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
...@@ -25,9 +25,6 @@ open class FullScreenSlideshowViewController: UIViewController { ...@@ -25,9 +25,6 @@ open class FullScreenSlideshowViewController: UIViewController {
/// Close button /// Close button
open var closeButton = UIButton() open var closeButton = UIButton()
/// Close button frame
open var closeButtonFrame: CGRect?
/// Closure called on page selection /// Closure called on page selection
open var pageSelected: ((_ page: Int) -> Void)? open var pageSelected: ((_ page: Int) -> Void)?
...@@ -62,7 +59,8 @@ open class FullScreenSlideshowViewController: UIViewController { ...@@ -62,7 +59,8 @@ open class FullScreenSlideshowViewController: UIViewController {
view.addSubview(slideshow) view.addSubview(slideshow)
// close button configuration // close button configuration
closeButton.setImage(UIImage(named: "ic_cross_white", in: Bundle(for: type(of: self)), compatibleWith: nil), for: UIControlState()) closeButton.frame = CGRect(x: 10, y: 20, width: 40, height: 40)
closeButton.setImage(UIImage(named: "Frameworks/ImageSlideshow.framework/ImageSlideshow.bundle/ic_cross_white@2x"), for: UIControlState())
closeButton.addTarget(self, action: #selector(FullScreenSlideshowViewController.close), for: UIControlEvents.touchUpInside) closeButton.addTarget(self, action: #selector(FullScreenSlideshowViewController.close), for: UIControlEvents.touchUpInside)
view.addSubview(closeButton) view.addSubview(closeButton)
} }
...@@ -80,24 +78,7 @@ open class FullScreenSlideshowViewController: UIViewController { ...@@ -80,24 +78,7 @@ open class FullScreenSlideshowViewController: UIViewController {
} }
} }
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
slideshow.slideshowItems.forEach { $0.cancelPendingLoad() }
}
open override func viewDidLayoutSubviews() { open override func viewDidLayoutSubviews() {
if !isBeingDismissed {
let safeAreaInsets: UIEdgeInsets
if #available(iOS 11.0, *) {
safeAreaInsets = view.safeAreaInsets
} else {
safeAreaInsets = UIEdgeInsets.zero
}
closeButton.frame = closeButtonFrame ?? CGRect(x: max(10, safeAreaInsets.left), y: max(10, safeAreaInsets.top), width: 40, height: 40)
}
slideshow.frame = view.frame slideshow.frame = view.frame
} }
......
...@@ -19,6 +19,17 @@ public enum PageControlPosition { ...@@ -19,6 +19,17 @@ public enum PageControlPosition {
case insideScrollView case insideScrollView
case underScrollView case underScrollView
case custom(padding: CGFloat) case custom(padding: CGFloat)
var bottomPadding: CGFloat {
switch self {
case .hidden, .insideScrollView:
return 0.0
case .underScrollView:
return 30.0
case .custom(let padding):
return padding
}
}
} }
/// Used to represent image preload strategy /// Used to represent image preload strategy
...@@ -35,63 +46,32 @@ public enum ImagePreload { ...@@ -35,63 +46,32 @@ public enum ImagePreload {
open class ImageSlideshow: UIView { open class ImageSlideshow: UIView {
/// Scroll View to wrap the slideshow /// Scroll View to wrap the slideshow
public let scrollView = UIScrollView() open let scrollView = UIScrollView()
/// Page Control shown in the slideshow /// Page Control shown in the slideshow
@available(*, deprecated, message: "Use pageIndicator.view instead") open let pageControl = UIPageControl()
open var pageControl: UIPageControl {
if let pageIndicator = pageIndicator as? UIPageControl {
return pageIndicator
}
fatalError("pageIndicator is not an instance of UIPageControl")
}
/// Activity indicator shown when loading image /// Activity indicator shown when loading image
open var activityIndicator: ActivityIndicatorFactory? { open var activityIndicator: ActivityIndicatorFactory? {
didSet { didSet {
reloadScrollView() self.reloadScrollView()
}
}
open var pageIndicator: PageIndicatorView? = UIPageControl() {
didSet {
oldValue?.view.removeFromSuperview()
if let pageIndicator = pageIndicator {
addSubview(pageIndicator.view)
}
setNeedsLayout()
}
}
open var pageIndicatorPosition: PageIndicatorPosition = PageIndicatorPosition() {
didSet {
setNeedsLayout()
} }
} }
// MARK: - State properties // MARK: - State properties
/// Page control position /// Page control position
@available(*, deprecated, message: "Use pageIndicatorPosition instead")
open var pageControlPosition = PageControlPosition.insideScrollView { open var pageControlPosition = PageControlPosition.insideScrollView {
didSet { didSet {
pageIndicator = UIPageControl() setNeedsLayout()
switch pageControlPosition {
case .hidden:
pageIndicator = nil
case .insideScrollView:
pageIndicatorPosition = PageIndicatorPosition(vertical: .bottom)
case .underScrollView:
pageIndicatorPosition = PageIndicatorPosition(vertical: .under)
case .custom(let padding):
pageIndicatorPosition = PageIndicatorPosition(vertical: .customUnder(padding: padding-30))
}
} }
} }
/// Current page /// Current page
open fileprivate(set) var currentPage: Int = 0 { open fileprivate(set) var currentPage: Int = 0 {
didSet { didSet {
pageControl.currentPage = currentPage
if oldValue != currentPage { if oldValue != currentPage {
currentPageChanged?(currentPage) currentPageChanged?(currentPage)
} }
...@@ -130,8 +110,8 @@ open class ImageSlideshow: UIView { ...@@ -130,8 +110,8 @@ open class ImageSlideshow: UIView {
/// Enables/disables infinite scrolling between images /// Enables/disables infinite scrolling between images
open var circular = true { open var circular = true {
didSet { didSet {
if images.count > 0 { if self.images.count > 0 {
setImageInputs(images) self.setImageInputs(self.images)
} }
} }
} }
...@@ -139,29 +119,22 @@ open class ImageSlideshow: UIView { ...@@ -139,29 +119,22 @@ open class ImageSlideshow: UIView {
/// Enables/disables user interactions /// Enables/disables user interactions
open var draggingEnabled = true { open var draggingEnabled = true {
didSet { didSet {
scrollView.isUserInteractionEnabled = draggingEnabled self.scrollView.isUserInteractionEnabled = draggingEnabled
} }
} }
/// Enables/disables zoom /// Enables/disables zoom
open var zoomEnabled = false { open var zoomEnabled = false {
didSet { didSet {
reloadScrollView() self.reloadScrollView()
}
}
/// Maximum zoom scale
open var maximumScale: CGFloat = 2.0 {
didSet {
reloadScrollView()
} }
} }
/// Image change interval, zero stops the auto-scrolling /// Image change interval, zero stops the auto-scrolling
open var slideshowInterval = 0.0 { open var slideshowInterval = 0.0 {
didSet { didSet {
slideshowTimer?.invalidate() self.slideshowTimer?.invalidate()
slideshowTimer = nil self.slideshowTimer = nil
setTimerIfNeeded() setTimerIfNeeded()
} }
} }
...@@ -170,7 +143,7 @@ open class ImageSlideshow: UIView { ...@@ -170,7 +143,7 @@ open class ImageSlideshow: UIView {
open var preload = ImagePreload.all open var preload = ImagePreload.all
/// Content mode of each image in the slideshow /// Content mode of each image in the slideshow
open var contentScaleMode: UIView.ContentMode = UIView.ContentMode.scaleAspectFit { open var contentScaleMode: UIViewContentMode = UIViewContentMode.scaleAspectFit {
didSet { didSet {
for view in slideshowItems { for view in slideshowItems {
view.imageView.contentMode = contentScaleMode view.imageView.contentMode = contentScaleMode
...@@ -183,10 +156,6 @@ open class ImageSlideshow: UIView { ...@@ -183,10 +156,6 @@ open class ImageSlideshow: UIView {
/// Transitioning delegate to manage the transition to full screen controller /// Transitioning delegate to manage the transition to full screen controller
open fileprivate(set) var slideshowTransitioningDelegate: ZoomAnimatedTransitioningDelegate? open fileprivate(set) var slideshowTransitioningDelegate: ZoomAnimatedTransitioningDelegate?
var primaryVisiblePage: Int {
return scrollView.frame.size.width > 0 ? Int(scrollView.contentOffset.x + scrollView.frame.size.width / 2) / Int(scrollView.frame.size.width) : 0
}
// MARK: - Life cycle // MARK: - Life cycle
...@@ -215,19 +184,11 @@ open class ImageSlideshow: UIView { ...@@ -215,19 +184,11 @@ open class ImageSlideshow: UIView {
scrollView.bounces = true scrollView.bounces = true
scrollView.showsHorizontalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false
scrollView.autoresizingMask = autoresizingMask scrollView.autoresizingMask = self.autoresizingMask
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
}
addSubview(scrollView) addSubview(scrollView)
if let pageIndicator = pageIndicator { addSubview(pageControl)
addSubview(pageIndicator.view) pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)
}
if let pageIndicator = pageIndicator as? UIControl {
pageIndicator.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)
}
setTimerIfNeeded() setTimerIfNeeded()
layoutScrollView() layoutScrollView()
...@@ -235,7 +196,7 @@ open class ImageSlideshow: UIView { ...@@ -235,7 +196,7 @@ open class ImageSlideshow: UIView {
open override func removeFromSuperview() { open override func removeFromSuperview() {
super.removeFromSuperview() super.removeFromSuperview()
pauseTimer() self.pauseTimer()
} }
open override func layoutSubviews() { open override func layoutSubviews() {
...@@ -249,49 +210,49 @@ open class ImageSlideshow: UIView { ...@@ -249,49 +210,49 @@ open class ImageSlideshow: UIView {
} }
open func layoutPageControl() { open func layoutPageControl() {
if let pageIndicatorView = pageIndicator?.view { if case .hidden = self.pageControlPosition {
pageIndicatorView.isHidden = images.count < 2 pageControl.isHidden = true
} else {
var edgeInsets: UIEdgeInsets = UIEdgeInsets.zero pageControl.isHidden = self.images.count < 2
if #available(iOS 11.0, *) { }
edgeInsets = safeAreaInsets
}
pageIndicatorView.sizeToFit() var pageControlBottomInset: CGFloat = 12.0
pageIndicatorView.frame = pageIndicatorPosition.indicatorFrame(for: frame, indicatorSize: pageIndicatorView.frame.size, edgeInsets: edgeInsets) if #available(iOS 11.0, *) {
pageControlBottomInset += self.safeAreaInsets.bottom
} }
pageControl.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: 10)
pageControl.center = CGPoint(x: frame.size.width / 2, y: frame.size.height - pageControlBottomInset)
} }
/// updates frame of the scroll view and its inner items /// updates frame of the scroll view and its inner items
func layoutScrollView() { func layoutScrollView() {
let pageIndicatorViewSize = pageIndicator?.view.frame.size let scrollViewBottomPadding: CGFloat = pageControlPosition.bottomPadding
let scrollViewBottomPadding = pageIndicatorViewSize.flatMap { pageIndicatorPosition.underPadding(for: $0) } ?? 0
scrollView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height - scrollViewBottomPadding) scrollView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height - scrollViewBottomPadding)
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(scrollViewImages.count), height: scrollView.frame.size.height) scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(scrollViewImages.count), height: scrollView.frame.size.height)
for (index, view) in slideshowItems.enumerated() { for (index, view) in self.slideshowItems.enumerated() {
if !view.zoomInInitially { if !view.zoomInInitially {
view.zoomOut() view.zoomOut()
} }
view.frame = CGRect(x: scrollView.frame.size.width * CGFloat(index), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height) view.frame = CGRect(x: scrollView.frame.size.width * CGFloat(index), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height)
} }
setScrollViewPage(scrollViewPage, animated: false) setCurrentPage(currentPage, animated: false)
} }
/// reloads scroll view with latest slideshow items /// reloads scroll view with latest slideshow items
func reloadScrollView() { func reloadScrollView() {
// remove previous slideshow items // remove previous slideshow items
for view in slideshowItems { for view in self.slideshowItems {
view.removeFromSuperview() view.removeFromSuperview()
} }
slideshowItems = [] self.slideshowItems = []
var i = 0 var i = 0
for image in scrollViewImages { for image in scrollViewImages {
let item = ImageSlideshowItem(image: image, zoomEnabled: zoomEnabled, activityIndicator: activityIndicator?.create(), maximumScale: maximumScale) let item = ImageSlideshowItem(image: image, zoomEnabled: self.zoomEnabled, activityIndicator: self.activityIndicator?.create())
item.imageView.contentMode = contentScaleMode item.imageView.contentMode = self.contentScaleMode
slideshowItems.append(item) slideshowItems.append(item)
scrollView.addSubview(item) scrollView.addSubview(item)
i += 1 i += 1
...@@ -312,7 +273,7 @@ open class ImageSlideshow: UIView { ...@@ -312,7 +273,7 @@ open class ImageSlideshow: UIView {
for i in 0..<totalCount { for i in 0..<totalCount {
let item = slideshowItems[i] let item = slideshowItems[i]
switch preload { switch self.preload {
case .all: case .all:
item.loadImage() item.loadImage()
case .fixed(let offset): case .fixed(let offset):
...@@ -333,8 +294,8 @@ open class ImageSlideshow: UIView { ...@@ -333,8 +294,8 @@ open class ImageSlideshow: UIView {
- parameter inputs: Array of InputSource instances. - parameter inputs: Array of InputSource instances.
*/ */
open func setImageInputs(_ inputs: [InputSource]) { open func setImageInputs(_ inputs: [InputSource]) {
images = inputs self.images = inputs
pageIndicator?.numberOfPages = inputs.count self.pageControl.numberOfPages = inputs.count
// in circular mode we add dummy first and last image to enable smooth scrolling // in circular mode we add dummy first and last image to enable smooth scrolling
if circular && images.count > 1 { if circular && images.count > 1 {
...@@ -348,9 +309,9 @@ open class ImageSlideshow: UIView { ...@@ -348,9 +309,9 @@ open class ImageSlideshow: UIView {
scImages.append(first) scImages.append(first)
} }
scrollViewImages = scImages self.scrollViewImages = scImages
} else { } else {
scrollViewImages = images self.scrollViewImages = images
} }
reloadScrollView() reloadScrollView()
...@@ -372,7 +333,7 @@ open class ImageSlideshow: UIView { ...@@ -372,7 +333,7 @@ open class ImageSlideshow: UIView {
pageOffset += 1 pageOffset += 1
} }
setScrollViewPage(pageOffset, animated: animated) self.setScrollViewPage(pageOffset, animated: animated)
} }
/** /**
...@@ -382,8 +343,8 @@ open class ImageSlideshow: UIView { ...@@ -382,8 +343,8 @@ open class ImageSlideshow: UIView {
*/ */
open func setScrollViewPage(_ newScrollViewPage: Int, animated: Bool) { open func setScrollViewPage(_ newScrollViewPage: Int, animated: Bool) {
if scrollViewPage < scrollViewImages.count { if scrollViewPage < scrollViewImages.count {
scrollView.scrollRectToVisible(CGRect(x: scrollView.frame.size.width * CGFloat(newScrollViewPage), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height), animated: animated) self.scrollView.scrollRectToVisible(CGRect(x: scrollView.frame.size.width * CGFloat(newScrollViewPage), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height), animated: animated)
setCurrentPageForScrollViewPage(newScrollViewPage) self.setCurrentPageForScrollViewPage(newScrollViewPage)
} }
} }
...@@ -394,14 +355,14 @@ open class ImageSlideshow: UIView { ...@@ -394,14 +355,14 @@ open class ImageSlideshow: UIView {
} }
@objc func slideshowTick(_ timer: Timer) { @objc func slideshowTick(_ timer: Timer) {
let page = scrollView.frame.size.width > 0 ? Int(scrollView.contentOffset.x / scrollView.frame.size.width) : 0 let page = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
var nextPage = page + 1 var nextPage = page + 1
if !circular && page == scrollViewImages.count - 1 { if !circular && page == scrollViewImages.count - 1 {
nextPage = 0 nextPage = 0
} }
setScrollViewPage(nextPage, animated: true) self.setScrollViewPage(nextPage, animated: true)
} }
fileprivate func setCurrentPageForScrollViewPage(_ page: Int) { fileprivate func setCurrentPageForScrollViewPage(_ page: Int) {
...@@ -416,22 +377,19 @@ open class ImageSlideshow: UIView { ...@@ -416,22 +377,19 @@ open class ImageSlideshow: UIView {
loadImages(for: page) loadImages(for: page)
} }
scrollViewPage = page scrollViewPage = page
currentPage = currentPageForScrollViewPage(page)
}
fileprivate func currentPageForScrollViewPage(_ page: Int) -> Int {
if circular { if circular {
if page == 0 { if page == 0 {
// first page contains the last image // first page contains the last image
return Int(images.count) - 1 currentPage = Int(images.count) - 1
} else if page == scrollViewImages.count - 1 { } else if page == scrollViewImages.count - 1 {
// last page contains the first image // last page contains the first image
return 0 currentPage = 0
} else { } else {
return page - 1 currentPage = page - 1
} }
} else { } else {
return page currentPage = page
} }
} }
...@@ -448,12 +406,12 @@ open class ImageSlideshow: UIView { ...@@ -448,12 +406,12 @@ open class ImageSlideshow: UIView {
@available(*, deprecated, message: "use pauseTimer instead") @available(*, deprecated, message: "use pauseTimer instead")
open func pauseTimerIfNeeded() { open func pauseTimerIfNeeded() {
pauseTimer() self.pauseTimer()
} }
@available(*, deprecated, message: "use unpauseTimer instead") @available(*, deprecated, message: "use unpauseTimer instead")
open func unpauseTimerIfNeeded() { open func unpauseTimerIfNeeded() {
unpauseTimer() self.unpauseTimer()
} }
/** /**
...@@ -464,12 +422,12 @@ open class ImageSlideshow: UIView { ...@@ -464,12 +422,12 @@ open class ImageSlideshow: UIView {
@discardableResult @discardableResult
open func presentFullScreenController(from controller: UIViewController) -> FullScreenSlideshowViewController { open func presentFullScreenController(from controller: UIViewController) -> FullScreenSlideshowViewController {
let fullscreen = FullScreenSlideshowViewController() let fullscreen = FullScreenSlideshowViewController()
fullscreen.pageSelected = {[weak self] (page: Int) in fullscreen.pageSelected = {(page: Int) in
self?.setCurrentPage(page, animated: false) self.setCurrentPage(page, animated: false)
} }
fullscreen.initialPage = currentPage fullscreen.initialPage = self.currentPage
fullscreen.inputs = images fullscreen.inputs = self.images
slideshowTransitioningDelegate = ZoomAnimatedTransitioningDelegate(slideshowView: self, slideshowController: fullscreen) slideshowTransitioningDelegate = ZoomAnimatedTransitioningDelegate(slideshowView: self, slideshowController: fullscreen)
fullscreen.transitioningDelegate = slideshowTransitioningDelegate fullscreen.transitioningDelegate = slideshowTransitioningDelegate
controller.present(fullscreen, animated: true, completion: nil) controller.present(fullscreen, animated: true, completion: nil)
...@@ -478,9 +436,7 @@ open class ImageSlideshow: UIView { ...@@ -478,9 +436,7 @@ open class ImageSlideshow: UIView {
} }
@objc private func pageControlValueChanged() { @objc private func pageControlValueChanged() {
if let currentPage = pageIndicator?.page { self.setCurrentPage(pageControl.currentPage, animated: true)
setCurrentPage(currentPage, animated: true)
}
} }
} }
...@@ -497,7 +453,8 @@ extension ImageSlideshow: UIScrollViewDelegate { ...@@ -497,7 +453,8 @@ extension ImageSlideshow: UIScrollViewDelegate {
} }
open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
setCurrentPageForScrollViewPage(primaryVisiblePage) let page = Int(scrollView.contentOffset.x) / Int(scrollView.frame.size.width)
setCurrentPageForScrollViewPage(page)
didEndDecelerating?() didEndDecelerating?()
} }
...@@ -511,7 +468,5 @@ extension ImageSlideshow: UIScrollViewDelegate { ...@@ -511,7 +468,5 @@ extension ImageSlideshow: UIScrollViewDelegate {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0) scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0)
} }
} }
pageIndicator?.page = currentPageForScrollViewPage(primaryVisiblePage)
} }
} }
...@@ -12,25 +12,22 @@ import UIKit ...@@ -12,25 +12,22 @@ import UIKit
open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate { open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate {
/// Image view to hold the image /// Image view to hold the image
public let imageView = UIImageView() open let imageView = UIImageView()
/// Activity indicator shown during image loading, when nil there won't be shown any /// Activity indicator shown during image loading, when nil there won't be shown any
public let activityIndicator: ActivityIndicatorView? open let activityIndicator: ActivityIndicatorView?
/// Input Source for the item /// Input Source for the item
public let image: InputSource open let image: InputSource
/// Guesture recognizer to detect double tap to zoom /// Guesture recognizer to detect double tap to zoom
open var gestureRecognizer: UITapGestureRecognizer? open var gestureRecognizer: UITapGestureRecognizer?
/// Holds if the zoom feature is enabled /// Holds if the zoom feature is enabled
public let zoomEnabled: Bool open let zoomEnabled: Bool
/// If set to true image is initially zoomed in /// If set to true image is initially zoomed in
open var zoomInInitially = false open var zoomInInitially = false
/// Maximum zoom scale
open var maximumScale: CGFloat = 2.0
fileprivate var lastFrame = CGRect.zero fileprivate var lastFrame = CGRect.zero
fileprivate var imageReleased = false fileprivate var imageReleased = false
...@@ -50,11 +47,10 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate { ...@@ -50,11 +47,10 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate {
- parameter image: Input Source to load the image - parameter image: Input Source to load the image
- parameter zoomEnabled: holds if it should be possible to zoom-in the image - parameter zoomEnabled: holds if it should be possible to zoom-in the image
*/ */
init(image: InputSource, zoomEnabled: Bool, activityIndicator: ActivityIndicatorView? = nil, maximumScale: CGFloat = 2.0) { init(image: InputSource, zoomEnabled: Bool, activityIndicator: ActivityIndicatorView? = nil) {
self.zoomEnabled = zoomEnabled self.zoomEnabled = zoomEnabled
self.image = image self.image = image
self.activityIndicator = activityIndicator self.activityIndicator = activityIndicator
self.maximumScale = maximumScale
super.init(frame: CGRect.null) super.init(frame: CGRect.null)
...@@ -120,34 +116,25 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate { ...@@ -120,34 +116,25 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate {
} }
/// Request to load Image Source to Image View /// Request to load Image Source to Image View
public func loadImage() { func loadImage() {
if self.imageView.image == nil && !isLoading { if self.imageView.image == nil && !isLoading {
isLoading = true isLoading = true
imageReleased = false imageReleased = false
activityIndicator?.show() activityIndicator?.show()
image.load(to: self.imageView) {[weak self] image in image.load(to: self.imageView) { image in
// set image to nil if there was a release request during the image load // set image to nil if there was a release request during the image load
if let imageRelease = self?.imageReleased, imageRelease { self.imageView.image = self.imageReleased ? nil : image
self?.imageView.image = nil self.activityIndicator?.hide()
}else{ self.loadFailed = image == nil
self?.imageView.image = image self.isLoading = false
}
self?.activityIndicator?.hide()
self?.loadFailed = image == nil
self?.isLoading = false
} }
} }
} }
func releaseImage() { func releaseImage() {
imageReleased = true imageReleased = true
cancelPendingLoad()
self.imageView.image = nil self.imageView.image = nil
} }
public func cancelPendingLoad() {
image.cancelLoad?(on: imageView)
}
@objc func retryLoadImage() { @objc func retryLoadImage() {
self.loadImage() self.loadImage()
...@@ -211,7 +198,8 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate { ...@@ -211,7 +198,8 @@ open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate {
} }
fileprivate func calculateMaximumScale() -> CGFloat { fileprivate func calculateMaximumScale() -> CGFloat {
return maximumScale // maximum scale is fixed to 2.0 for now. This may be overriden to perform a more sophisticated computation
return 2.0
} }
fileprivate func setPictoCenter() { fileprivate func setPictoCenter() {
......
...@@ -12,17 +12,11 @@ import UIKit ...@@ -12,17 +12,11 @@ import UIKit
@objc public protocol InputSource { @objc public protocol InputSource {
/** /**
Load image from the source to image view. Load image from the source to image view.
- parameter imageView: Image view to load the image into. - parameter imageView: The image view to load the image into.
- parameter callback: Callback called after image was set to the image view. - parameter callback: Callback called after the image was set to the image view.
- parameter image: Image that was set to the image view. - parameter image: Image that was set to the image view.
*/ */
func load(to imageView: UIImageView, with callback: @escaping (_ image: UIImage?) -> Void) func load(to imageView: UIImageView, with callback: @escaping (_ image: UIImage?) -> Void)
/**
Cancel image load on the image view
- parameter imageView: Image view that is loading the image
*/
@objc optional func cancelLoad(on imageView: UIImageView)
} }
/// Input Source to load plain UIImage /// Input Source to load plain UIImage
......
//
// PageIndicator.swift
// ImageSlideshow
//
// Created by Petr Zvoníček on 27.05.18.
//
import UIKit
/// Cusotm Page Indicator can be used by implementing this protocol
public protocol PageIndicatorView: class {
/// View of the page indicator
var view: UIView { get }
/// Current page of the page indicator
var page: Int { get set }
/// Total number of pages of the page indicator
var numberOfPages: Int { get set}
}
extension UIPageControl: PageIndicatorView {
public var view: UIView {
return self
}
public var page: Int {
get {
return currentPage
}
set {
currentPage = newValue
}
}
open override func sizeToFit() {
var frame = self.frame
frame.size = size(forNumberOfPages: numberOfPages)
frame.size.height = 30
self.frame = frame
}
}
/// Page indicator that shows page in numeric style, eg. "5/21"
public class LabelPageIndicator: UILabel, PageIndicatorView {
public var view: UIView {
return self
}
public var numberOfPages: Int = 0 {
didSet {
updateLabel()
}
}
public var page: Int = 0 {
didSet {
updateLabel()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
self.textAlignment = .center
}
private func updateLabel() {
text = "\(page+1)/\(numberOfPages)"
}
public override func sizeToFit() {
let maximumString = String(repeating: "8", count: numberOfPages) as NSString
self.frame.size = maximumString.size(withAttributes: [.font: font])
}
}
//
// PageIndicator.swift
// ImageSlideshow
//
// Created by Petr Zvoníček on 04.02.18.
//
import UIKit
/// Describes the configuration of the page indicator position
public struct PageIndicatorPosition {
public enum Horizontal {
case left(padding: CGFloat), center, right(padding: CGFloat)
}
public enum Vertical {
case top, bottom, under, customTop(padding: CGFloat), customBottom(padding: CGFloat), customUnder(padding: CGFloat)
}
/// Horizontal position of the page indicator
var horizontal: Horizontal
/// Vertical position of the page indicator
var vertical: Vertical
/// Creates a new PageIndicatorPosition struct
///
/// - Parameters:
/// - horizontal: horizontal position of the page indicator
/// - vertical: vertical position of the page indicator
public init(horizontal: Horizontal = .center, vertical: Vertical = .bottom) {
self.horizontal = horizontal
self.vertical = vertical
}
/// Computes the additional padding needed for the page indicator under the ImageSlideshow
///
/// - Parameter indicatorSize: size of the page indicator
/// - Returns: padding needed under the ImageSlideshow
func underPadding(for indicatorSize: CGSize) -> CGFloat {
switch vertical {
case .under:
return indicatorSize.height
case .customUnder(let padding):
return indicatorSize.height + padding
default:
return 0
}
}
/// Computes the page indicator frame
///
/// - Parameters:
/// - parentFrame: frame of the parent view – ImageSlideshow
/// - indicatorSize: size of the page indicator
/// - edgeInsets: edge insets of the parent view – ImageSlideshow (used for SafeAreaInsets adjustment)
/// - Returns: frame of the indicator by computing the origin and using `indicatorSize` as size
func indicatorFrame(for parentFrame: CGRect, indicatorSize: CGSize, edgeInsets: UIEdgeInsets) -> CGRect {
var xSize: CGFloat = 0
var ySize: CGFloat = 0
switch horizontal {
case .center:
xSize = parentFrame.size.width / 2 - indicatorSize.width / 2
case .left(let padding):
xSize = padding + edgeInsets.left
case .right(let padding):
xSize = parentFrame.size.width - indicatorSize.width - padding - edgeInsets.right
}
switch vertical {
case .bottom, .under, .customUnder:
ySize = parentFrame.size.height - indicatorSize.height - edgeInsets.bottom
case .customBottom(let padding):
ySize = parentFrame.size.height - indicatorSize.height - padding - edgeInsets.bottom
case .top:
ySize = edgeInsets.top
case .customTop(let padding):
ySize = padding + edgeInsets.top
}
return CGRect(x: xSize, y: ySize, width: indicatorSize.width, height: indicatorSize.height)
}
}
//
// SwiftSupport.swift
// ImageSlideshow
//
// Created by Pierluigi Cifani on 30/07/2018.
//
import UIKit
#if swift(>=4.2)
typealias UIViewContentMode = UIView.ContentMode
typealias UIActivityIndicatorViewStyle = UIActivityIndicatorView.Style
typealias UIControlState = UIControl.State
typealias UIViewAnimationOptions = UIView.AnimationOptions
typealias UIControlEvents = UIControl.Event
typealias UIViewAutoresizing = UIView.AutoresizingMask
#else
#endif
...@@ -13,7 +13,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti ...@@ -13,7 +13,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti
/// parent image view used for animated transition /// parent image view used for animated transition
open var referenceImageView: UIImageView? open var referenceImageView: UIImageView?
/// parent slideshow view used for animated transition /// parent slideshow view used for animated transition
open weak var referenceSlideshowView: ImageSlideshow? open var referenceSlideshowView: ImageSlideshow?
// must be weak because FullScreenSlideshowViewController has strong reference to its transitioning delegate // must be weak because FullScreenSlideshowViewController has strong reference to its transitioning delegate
weak var referenceSlideshowController: FullScreenSlideshowViewController? weak var referenceSlideshowController: FullScreenSlideshowViewController?
...@@ -66,7 +66,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti ...@@ -66,7 +66,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti
return return
} }
let percent = min(max(abs(gesture.translation(in: gesture.view!).y) / 200.0, 0.0), 1.0) let percent = min(max(fabs(gesture.translation(in: gesture.view!).y) / 200.0, 0.0), 1.0)
if gesture.state == .began { if gesture.state == .began {
interactionController = UIPercentDrivenInteractiveTransition() interactionController = UIPercentDrivenInteractiveTransition()
...@@ -74,9 +74,10 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti ...@@ -74,9 +74,10 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti
} else if gesture.state == .changed { } else if gesture.state == .changed {
interactionController?.update(percent) interactionController?.update(percent)
} else if gesture.state == .ended || gesture.state == .cancelled || gesture.state == .failed { } else if gesture.state == .ended || gesture.state == .cancelled || gesture.state == .failed {
let velocity = gesture.velocity(in: referenceSlideshowView) let velocity = gesture.velocity(in: referenceSlideshowView)
if abs(velocity.y) > 500 { if fabs(velocity.y) > 500 {
if let pageSelected = referenceSlideshowController.pageSelected { if let pageSelected = referenceSlideshowController.pageSelected {
pageSelected(referenceSlideshowController.slideshow.currentPage) pageSelected(referenceSlideshowController.slideshow.currentPage)
} }
...@@ -88,6 +89,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti ...@@ -88,6 +89,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti
} }
interactionController?.finish() interactionController?.finish()
} else { } else {
interactionController?.cancel() interactionController?.cancel()
} }
...@@ -127,7 +129,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti ...@@ -127,7 +129,7 @@ open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransiti
extension ZoomAnimatedTransitioningDelegate: UIGestureRecognizerDelegate { extension ZoomAnimatedTransitioningDelegate: UIGestureRecognizerDelegate {
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { guard let _ = gestureRecognizer as? UIPanGestureRecognizer else {
return false return false
} }
...@@ -139,11 +141,6 @@ extension ZoomAnimatedTransitioningDelegate: UIGestureRecognizerDelegate { ...@@ -139,11 +141,6 @@ extension ZoomAnimatedTransitioningDelegate: UIGestureRecognizerDelegate {
return false return false
} }
if let view = gestureRecognizer.view {
let velocity = gestureRecognizer.velocity(in: view)
return abs(velocity.x) < abs(velocity.y)
}
return true return true
} }
} }
...@@ -170,7 +167,9 @@ class ZoomAnimator: NSObject { ...@@ -170,7 +167,9 @@ class ZoomAnimator: NSObject {
} }
@objcMembers @objcMembers
class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { class ZoomInAnimator: ZoomAnimator { }
extension ZoomInAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5 return 0.5
...@@ -192,14 +191,8 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -192,14 +191,8 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
let transitionBackgroundView = UIView(frame: containerView.frame) let transitionBackgroundView = UIView(frame: containerView.frame)
transitionBackgroundView.backgroundColor = toViewController.backgroundColor transitionBackgroundView.backgroundColor = toViewController.backgroundColor
containerView.addSubview(transitionBackgroundView) containerView.addSubview(transitionBackgroundView)
#if swift(>=4.2)
containerView.sendSubviewToBack(transitionBackgroundView)
#else
containerView.sendSubview(toBack: transitionBackgroundView) containerView.sendSubview(toBack: transitionBackgroundView)
#endif
let finalFrame = toViewController.view.frame let finalFrame = toViewController.view.frame
var transitionView: UIImageView? var transitionView: UIImageView?
...@@ -229,9 +222,9 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -229,9 +222,9 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
fromViewController.view.alpha = 0 fromViewController.view.alpha = 0
transitionView?.frame = transitionViewFinalFrame transitionView?.frame = transitionViewFinalFrame
transitionView?.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY) transitionView?.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}, completion: {[ref = self.referenceImageView] _ in }, completion: {(_) in
fromViewController.view.alpha = 1 fromViewController.view.alpha = 1
ref?.alpha = 1 self.referenceImageView?.alpha = 1
transitionView?.removeFromSuperview() transitionView?.removeFromSuperview()
transitionBackgroundView.removeFromSuperview() transitionBackgroundView.removeFromSuperview()
containerView.addSubview(toViewController.view) containerView.addSubview(toViewController.view)
...@@ -240,35 +233,19 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -240,35 +233,19 @@ class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
} }
} }
class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { class ZoomOutAnimator: ZoomAnimator { }
private var animatorForCurrentTransition: UIViewImplicitlyAnimating? extension ZoomOutAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25 return 0.25
} }
@available(iOS 10.0, *) func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
// as per documentation, the same object should be returned for the ongoing transition
if let animatorForCurrentSession = animatorForCurrentTransition {
return animatorForCurrentSession
}
let params = animationParams(using: transitionContext)
let animator = UIViewPropertyAnimator(duration: params.0, curve: .linear, animations: params.1)
animator.addCompletion(params.2)
animatorForCurrentTransition = animator
return animator
}
private func animationParams(using transitionContext: UIViewControllerContextTransitioning) -> (TimeInterval, () -> (), (Any) -> ()) {
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? FullScreenSlideshowViewController else { guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? FullScreenSlideshowViewController else {
fatalError("Transition not used with FullScreenSlideshowViewController") return
} }
let containerView = transitionContext.containerView let containerView = transitionContext.containerView
...@@ -276,11 +253,8 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -276,11 +253,8 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
toViewController.view.frame = transitionContext.finalFrame(for: toViewController) toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
toViewController.view.alpha = 0 toViewController.view.alpha = 0
containerView.addSubview(toViewController.view) containerView.addSubview(toViewController.view)
#if swift(>=4.2)
containerView.sendSubviewToBack(toViewController.view)
#else
containerView.sendSubview(toBack: toViewController.view) containerView.sendSubview(toBack: toViewController.view)
#endif
var transitionViewInitialFrame: CGRect var transitionViewInitialFrame: CGRect
if let currentSlideshowItem = fromViewController.slideshow.currentSlideshowItem { if let currentSlideshowItem = fromViewController.slideshow.currentSlideshowItem {
if let image = currentSlideshowItem.imageView.image { if let image = currentSlideshowItem.imageView.image {
...@@ -316,11 +290,7 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -316,11 +290,7 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
let transitionBackgroundView = UIView(frame: containerView.frame) let transitionBackgroundView = UIView(frame: containerView.frame)
transitionBackgroundView.backgroundColor = fromViewController.backgroundColor transitionBackgroundView.backgroundColor = fromViewController.backgroundColor
containerView.addSubview(transitionBackgroundView) containerView.addSubview(transitionBackgroundView)
#if swift(>=4.2)
containerView.sendSubviewToBack(transitionBackgroundView)
#else
containerView.sendSubview(toBack: transitionBackgroundView) containerView.sendSubview(toBack: transitionBackgroundView)
#endif
let transitionView: UIImageView = UIImageView(image: fromViewController.slideshow.currentSlideshowItem?.imageView.image) let transitionView: UIImageView = UIImageView(image: fromViewController.slideshow.currentSlideshowItem?.imageView.image)
transitionView.contentMode = UIViewContentMode.scaleAspectFill transitionView.contentMode = UIViewContentMode.scaleAspectFill
...@@ -350,21 +320,14 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { ...@@ -350,21 +320,14 @@ class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning {
transitionView.removeFromSuperview() transitionView.removeFromSuperview()
transitionBackgroundView.removeFromSuperview() transitionBackgroundView.removeFromSuperview()
self.animatorForCurrentTransition = nil
transitionContext.completeTransition(completed) transitionContext.completeTransition(completed)
} }
return (duration, animations, completion) // Working around iOS 10 bug in UIView.animate causing a glitch in interrupted interactive transition
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Working around iOS 10+ breaking change requiring to use UIPropertyAnimator for proper interactive transition instead of UIView.animate
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
interruptibleAnimator(using: transitionContext).startAnimation() UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: animations, completion: completion)
} else { } else {
let params = animationParams(using: transitionContext) UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: animations, completion: completion)
UIView.animate(withDuration: params.0, delay: 0, options: UIViewAnimationOptions(), animations: params.1, completion: params.2)
} }
} }
} }
...@@ -44,8 +44,4 @@ public class SDWebImageSource: NSObject, InputSource { ...@@ -44,8 +44,4 @@ public class SDWebImageSource: NSObject, InputSource {
callback(image) callback(image)
}) })
} }
public func cancelLoad(on imageView: UIImageView) {
imageView.sd_cancelCurrentImageLoad()
}
} }
# 🖼 ImageSlideshow # 🖼 ImageSlideshow
**Customizable Swift image slideshow with circular scrolling, timer and full screen viewer** **Swift image slideshow with circular scrolling, timer and full screen viewer**
[![Build Status](https://www.bitrise.io/app/9aaf3e552f3a575c.svg?token=AjiVckTN9ItQtJs873mYMw&branch=master)](https://www.bitrise.io/app/9aaf3e552f3a575c) [![Build Status](https://www.bitrise.io/app/9aaf3e552f3a575c.svg?token=AjiVckTN9ItQtJs873mYMw&branch=master)](https://www.bitrise.io/app/9aaf3e552f3a575c)
[![Version](https://img.shields.io/cocoapods/v/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) [![Version](https://img.shields.io/cocoapods/v/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow)
...@@ -8,9 +8,7 @@ ...@@ -8,9 +8,7 @@
[![License](https://img.shields.io/cocoapods/l/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) [![License](https://img.shields.io/cocoapods/l/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow)
[![Platform](https://img.shields.io/cocoapods/p/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) [![Platform](https://img.shields.io/cocoapods/p/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow)
![](http://cl.ly/image/2v193I0G0h0Z/ImageSlideshow2.gif)
![](https://dzwonsemrish7.cloudfront.net/items/2R06283n040V3P3p0i42/ezgif.com-optimize.gif)
## 📱 Example ## 📱 Example
...@@ -23,26 +21,26 @@ ImageSlideshow is available through [CocoaPods](http://cocoapods.org). To instal ...@@ -23,26 +21,26 @@ ImageSlideshow is available through [CocoaPods](http://cocoapods.org). To instal
it, simply add the following line to your Podfile: it, simply add the following line to your Podfile:
```ruby ```ruby
pod 'ImageSlideshow', '~> 1.6' pod 'ImageSlideshow', '~> 1.3'
``` ```
### Carthage ### Carthage
To integrate ImageSlideshow into your Xcode project using Carthage, specify it in your Cartfile: To integrate ImageSlideshow into your Xcode project using Carthage, specify it in your Cartfile:
```ruby ```ruby
github "zvonicek/ImageSlideshow" ~> 1.6 github "zvonicek/ImageSlideshow" "1.3"
``` ```
Carthage does not include InputSources for external providers (due to dependency on those providers) so you need to grab the one you need from `ImageSlideshow/Classes/InputSources` manually. Carthage does not include InputSources for external providers (due to dependency on those providers) so you need to grab the one you need from `ImageSlideshow/Classes/InputSources` manually.
### Manually ### Manually
One possibility is to download a built framework (ImageSlideshow.framework.zip) from [releases page](https://github.com/zvonicek/ImageSlideshow/releases/) and link it with your project (under`Linked Frameworks and Libraries` in your target). This is, however, currently problematic because of rapid Swift development -- the framework is built for a single Swift version and may not work on previous/future versions. One possibility is to download a builded framework (ImageSlideshow.framework.zip) from [releases page](https://github.com/zvonicek/ImageSlideshow/releases/) and link it with your project (under`Linked Frameworks and Libraries` in your target). This is, however, currently problematic because of rapid Swift development -- the framework is builded for a single Swift version and may not work on previous/future versions.
Alternatively can also grab the whole `ImageSlideshow` directory and copy it to your project. Be sure to remove those external Input Sources you don't need. Alternatively can also grab the whole `ImageSlideshow` directory and copy it to your project. Be sure to remove those external Input Sources you don't need.
**Note on Swift 2.3, Swift 3 and Swift 4 support** **Note on Swift 2.3 and Swift 3 support**
Version 1.4 supports Swift 4. Swift 3 is supported from version 1.0, for Swift 2.2 and Swift 2.3 compatible code use version 0.6 or branch *swift-2.3*. Version 1.0 supports Swift 3. For Swift 2.2 and Swift 2.3 compatible code use version 0.6 or branch *swift-2.3*.
## 🔨 How to use ## 🔨 How to use
...@@ -79,9 +77,7 @@ Behaviour is configurable by those properties: ...@@ -79,9 +77,7 @@ Behaviour is configurable by those properties:
- ```slideshowInterval``` - slideshow interval in seconds (default `0` – disabled) - ```slideshowInterval``` - slideshow interval in seconds (default `0` – disabled)
- ```zoomEnabled``` - enables zooming (default `false`) - ```zoomEnabled``` - enables zooming (default `false`)
- ```circular``` - enables circular scrolling (default `true`) - ```circular``` - enables circular scrolling (default `true`)
- ```activityIndicator``` – allows to set custom activity indicator, see *Activity indicator* section - ```pageControlPosition``` - configures position of UIPageControl (default `insideScrollView`, also `hidden`, `underScrollView` or `custom`)
- ```pageIndicator``` – allows to set custom page indicator, see *Page indicator* section; assign `nil` to hide page indicator
- ```pageIndicatorPosition``` - configures position of the page indicator
- ```contentScaleMode``` - configures the scaling (default `ScaleAspectFit`) - ```contentScaleMode``` - configures the scaling (default `ScaleAspectFit`)
- ```draggingEnabled``` - enables dragging (default `true`) - ```draggingEnabled``` - enables dragging (default `true`)
- ```currentPageChanged``` - closure called on page change - ```currentPageChanged``` - closure called on page change
...@@ -89,37 +85,6 @@ Behaviour is configurable by those properties: ...@@ -89,37 +85,6 @@ Behaviour is configurable by those properties:
- ```didEndDecelerating``` - closure called on scrollViewDidEndDecelerating - ```didEndDecelerating``` - closure called on scrollViewDidEndDecelerating
- ```preload``` - image preloading configuration (default `all` preloading, also `fixed`) - ```preload``` - image preloading configuration (default `all` preloading, also `fixed`)
### Page Indicator
Page indicator can be customized using the `pageIndicator` property on ImageSlideshow. By defualt, a plain UIPageControl is used. If needed, page control can be customized:
```swift
let pageIndicator = UIPageControl()
pageIndicator.currentPageIndicatorTintColor = UIColor.lightGray
pageIndicator.pageIndicatorTintColor = UIColor.black
slideshow.pageIndicator = pageIndicator
```
Also, a simple label page indicator that shows pages in style "5/21" (fifth page from twenty one) is provided:
```swift
slideshow.pageIndicator = LabelPageIndicator()
```
You can also use your own page indicator by adopting the `PageIndicatorView` protocol.
Position of the page indicator can be configured by assigning a `PageIndicatorPosition` value to the `pageIndicatorPosition` property on ImageSlideshow. You may specify the horizontal and vertical positioning separately.
**Horizontal** positioning options are: `.left(padding: Int)`, `.center`, `.right(padding: Int)`
**Vertical** positioning options are: `.top`, `.bottom`, `.under`, `customTop(padding: Int)`, `customBottom(padding: Int)`, `customUnder(padding: Int)`
Example:
```swift
slideshow.pageIndicatorPosition = PageIndicatorPosition(horizontal: .left(padding: 20), vertical: .bottom)
```
### Activity Indicator ### Activity Indicator
By default activity indicator is not shown, but you can enable it by setting `DefaultActivityIndicator` instance to Image Slideshow: By default activity indicator is not shown, but you can enable it by setting `DefaultActivityIndicator` instance to Image Slideshow:
......
...@@ -51,12 +51,12 @@ PODS: ...@@ -51,12 +51,12 @@ PODS:
- "GoogleToolboxForMac/NSData+zlib (2.1.4)": - "GoogleToolboxForMac/NSData+zlib (2.1.4)":
- GoogleToolboxForMac/Defines (= 2.1.4) - GoogleToolboxForMac/Defines (= 2.1.4)
- GTMSessionFetcher/Core (1.1.15) - GTMSessionFetcher/Core (1.1.15)
- ImageSlideshow (1.7.0): - ImageSlideshow (1.4.1):
- ImageSlideshow/Core (= 1.7.0) - ImageSlideshow/Core (= 1.4.1)
- ImageSlideshow/Core (1.7.0) - ImageSlideshow/Core (1.4.1)
- ImageSlideshow/SDWebImage (1.7.0): - ImageSlideshow/SDWebImage (1.4.1):
- ImageSlideshow/Core - ImageSlideshow/Core
- SDWebImage (< 5.0, >= 3.7) - SDWebImage (~> 3.7)
- INSPhotoGallery (1.2.5) - INSPhotoGallery (1.2.5)
- IQKeyboardManagerSwift (5.0.8) - IQKeyboardManagerSwift (5.0.8)
- Kingfisher (4.7.0) - Kingfisher (4.7.0)
...@@ -74,9 +74,9 @@ PODS: ...@@ -74,9 +74,9 @@ PODS:
- Realm/Headers (3.11.0) - Realm/Headers (3.11.0)
- RealmSwift (3.11.0): - RealmSwift (3.11.0):
- Realm (= 3.11.0) - Realm (= 3.11.0)
- SDWebImage (4.4.2): - SDWebImage (3.8.2):
- SDWebImage/Core (= 4.4.2) - SDWebImage/Core (= 3.8.2)
- SDWebImage/Core (4.4.2) - SDWebImage/Core (3.8.2)
- SVProgressHUD (2.0.3) - SVProgressHUD (2.0.3)
- SwiftDate (4.4.1) - SwiftDate (4.4.1)
- TimeAgoInWords (3.0.0) - TimeAgoInWords (3.0.0)
...@@ -156,7 +156,7 @@ SPEC CHECKSUMS: ...@@ -156,7 +156,7 @@ SPEC CHECKSUMS:
FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2 GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2
ImageSlideshow: f8eec5e37a980f60923b585ff23b0e140fd24018 ImageSlideshow: 1bb0e610e28a71e8936d57265d882f61714a470b
INSPhotoGallery: 8bd5b434e70d06dd698085f8e865f6602d82014b INSPhotoGallery: 8bd5b434e70d06dd698085f8e865f6602d82014b
IQKeyboardManagerSwift: 2e7dc7f98c111458c1ea2b373f893e8cf95e2b97 IQKeyboardManagerSwift: 2e7dc7f98c111458c1ea2b373f893e8cf95e2b97
Kingfisher: da6b005aa96d37698e3e4f1ccfe96a5b9bbf27d6 Kingfisher: da6b005aa96d37698e3e4f1ccfe96a5b9bbf27d6
...@@ -167,7 +167,7 @@ SPEC CHECKSUMS: ...@@ -167,7 +167,7 @@ SPEC CHECKSUMS:
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc
RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681 SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c
SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4 SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4
SwiftDate: a70a534d4feed03b2a2b575e36bef82dc2286a8d SwiftDate: a70a534d4feed03b2a2b575e36bef82dc2286a8d
TimeAgoInWords: 633dbb30810de855333dedd1d5033d28b1ecfd6f TimeAgoInWords: 633dbb30810de855333dedd1d5033d28b1ecfd6f
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -7,33 +7,30 @@ ...@@ -7,33 +7,30 @@
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "YES">
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "9FB26439C46101C637E927BC50978C09" BlueprintIdentifier = "9FB26439C46101C637E927BC50978C09"
BuildableName = "INSPhotoGallery.bundle"
BlueprintName = "INSPhotoGallery-INSPhotoGallery" BlueprintName = "INSPhotoGallery-INSPhotoGallery"
ReferencedContainer = "container:Pods.xcodeproj"> ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "INSPhotoGallery-INSPhotoGallery.bundle">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
<Testables> buildConfiguration = "Debug">
</Testables>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
...@@ -41,25 +38,17 @@ ...@@ -41,25 +38,17 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9FB26439C46101C637E927BC50978C09"
BuildableName = "INSPhotoGallery.bundle"
BlueprintName = "INSPhotoGallery-INSPhotoGallery"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC9AB3FA3139B764F352D5BCC210DDB7"
BlueprintName = "ImageSlideshow-ImageSlideshow"
ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "ImageSlideshow-ImageSlideshow.bundle">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
buildForArchiving = "YES"> buildForArchiving = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "64497E66D96A364F588D8C1876566C53" BlueprintIdentifier = "2339AFBD67988C09BB6EE88F18CFF581"
BlueprintName = "ImageSlideshow" BlueprintName = "ImageSlideshow"
ReferencedContainer = "container:Pods.xcodeproj" ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "ImageSlideshow.framework"> BuildableName = "ImageSlideshow.framework">
......
...@@ -7,33 +7,30 @@ ...@@ -7,33 +7,30 @@
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "YES">
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "F97FDCB797E5DF04953162D3549D7A26" BlueprintIdentifier = "F97FDCB797E5DF04953162D3549D7A26"
BuildableName = "Pods_Bhagyashree.framework"
BlueprintName = "Pods-Bhagyashree" BlueprintName = "Pods-Bhagyashree"
ReferencedContainer = "container:Pods.xcodeproj"> ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "Pods-Bhagyashree.framework">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
<Testables> buildConfiguration = "Debug">
</Testables>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
...@@ -41,25 +38,17 @@ ...@@ -41,25 +38,17 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F97FDCB797E5DF04953162D3549D7A26"
BuildableName = "Pods_Bhagyashree.framework"
BlueprintName = "Pods-Bhagyashree"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
buildForArchiving = "YES"> buildForArchiving = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "2E07C16B760AF46E91B3D867A59A4952" BlueprintIdentifier = "6B8583CCE3BE778FE818B271F73B3053"
BlueprintName = "SDWebImage" BlueprintName = "SDWebImage"
ReferencedContainer = "container:Pods.xcodeproj" ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "SDWebImage.framework"> BuildableName = "SDWebImage.framework">
......
...@@ -7,33 +7,30 @@ ...@@ -7,33 +7,30 @@
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "YES">
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "94117655CE92C34C54AE3BF2F2BC4C75" BlueprintIdentifier = "94117655CE92C34C54AE3BF2F2BC4C75"
BuildableName = "TimeAgoInWords.bundle"
BlueprintName = "TimeAgoInWords-TimeAgoInWords" BlueprintName = "TimeAgoInWords-TimeAgoInWords"
ReferencedContainer = "container:Pods.xcodeproj"> ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "TimeAgoInWords-TimeAgoInWords.bundle">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
<Testables> buildConfiguration = "Debug">
</Testables>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
...@@ -41,25 +38,17 @@ ...@@ -41,25 +38,17 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "94117655CE92C34C54AE3BF2F2BC4C75"
BuildableName = "TimeAgoInWords.bundle"
BlueprintName = "TimeAgoInWords-TimeAgoInWords"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
......
...@@ -7,33 +7,30 @@ ...@@ -7,33 +7,30 @@
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "YES">
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "34371692C0577426FE7C41894182EEA5" BlueprintIdentifier = "34371692C0577426FE7C41894182EEA5"
BuildableName = "leveldb.framework"
BlueprintName = "leveldb-library" BlueprintName = "leveldb-library"
ReferencedContainer = "container:Pods.xcodeproj"> ReferencedContainer = "container:Pods.xcodeproj"
BuildableName = "leveldb-library.framework">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
<Testables> buildConfiguration = "Debug">
</Testables>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
...@@ -41,25 +38,17 @@ ...@@ -41,25 +38,17 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "34371692C0577426FE7C41894182EEA5"
BuildableName = "leveldb.framework"
BlueprintName = "leveldb-library"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
......
...@@ -8,197 +8,146 @@ ...@@ -8,197 +8,146 @@
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>6</integer>
</dict> </dict>
<key>EasyToast.xcscheme</key> <key>EasyToast.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>7</integer>
</dict> </dict>
<key>FirebaseAuth.xcscheme</key> <key>FirebaseAuth.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>8</integer>
</dict> </dict>
<key>FirebaseCore.xcscheme</key> <key>FirebaseCore.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>9</integer>
</dict> </dict>
<key>FirebaseDatabase.xcscheme</key> <key>FirebaseDatabase.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>10</integer>
</dict> </dict>
<key>FirebaseMessaging.xcscheme</key> <key>FirebaseMessaging.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>11</integer>
</dict> </dict>
<key>FirebaseStorage.xcscheme</key> <key>FirebaseStorage.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>12</integer>
</dict> </dict>
<key>GTMSessionFetcher.xcscheme</key> <key>GTMSessionFetcher.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>14</integer>
</dict> </dict>
<key>GoogleToolboxForMac.xcscheme</key> <key>GoogleToolboxForMac.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>13</integer>
</dict> </dict>
<key>INSPhotoGallery-INSPhotoGallery.xcscheme</key> <key>INSPhotoGallery-INSPhotoGallery.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>17</integer>
</dict> </dict>
<key>INSPhotoGallery.xcscheme</key> <key>INSPhotoGallery.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>16</integer>
</dict> </dict>
<key>IQKeyboardManagerSwift.xcscheme</key> <key>IQKeyboardManagerSwift.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key> </dict>
<integer>18</integer> <key>ImageSlideshow-ImageSlideshow.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict> </dict>
<key>ImageSlideshow.xcscheme</key> <key>ImageSlideshow.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>15</integer>
</dict> </dict>
<key>Kingfisher.xcscheme</key> <key>Kingfisher.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>19</integer>
</dict> </dict>
<key>MJRefresh.xcscheme</key> <key>MJRefresh.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>21</integer>
</dict> </dict>
<key>Pastel.xcscheme</key> <key>Pastel.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>23</integer>
</dict> </dict>
<key>Pods-Bhagyashree.xcscheme</key> <key>Pods-Bhagyashree.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>24</integer>
</dict> </dict>
<key>Protobuf.xcscheme</key> <key>Protobuf.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>25</integer>
</dict> </dict>
<key>Realm.xcscheme</key> <key>Realm.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>26</integer>
</dict> </dict>
<key>RealmSwift.xcscheme</key> <key>RealmSwift.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>27</integer>
</dict> </dict>
<key>SDWebImage.xcscheme</key> <key>SDWebImage.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>28</integer>
</dict> </dict>
<key>SVProgressHUD.xcscheme</key> <key>SVProgressHUD.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>29</integer>
</dict> </dict>
<key>SwiftDate.xcscheme</key> <key>SwiftDate.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>30</integer>
</dict> </dict>
<key>TimeAgoInWords-TimeAgoInWords.xcscheme</key> <key>TimeAgoInWords-TimeAgoInWords.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>32</integer>
</dict> </dict>
<key>TimeAgoInWords.xcscheme</key> <key>TimeAgoInWords.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>31</integer>
</dict> </dict>
<key>WMPageController.xcscheme</key> <key>WMPageController.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>33</integer>
</dict> </dict>
<key>leveldb-library.xcscheme</key> <key>leveldb-library.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>20</integer>
</dict> </dict>
<key>nanopb.xcscheme</key> <key>nanopb.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key>
<integer>22</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>
......
Copyright (c) 2009-2017 Olivier Poitrey rs@dailymotion.com Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
<p align="center" > Web Image
<img src="SDWebImage_logo.png" title="SDWebImage logo" float=left> =========
</p>
[![Build Status](http://img.shields.io/travis/rs/SDWebImage/master.svg?style=flat)](https://travis-ci.org/rs/SDWebImage) [![Build Status](http://img.shields.io/travis/rs/SDWebImage/master.svg?style=flat)](https://travis-ci.org/rs/SDWebImage)
[![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Dependency Status](https://www.versioneye.com/objective-c/sdwebimage/badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage) [![Dependency Status](https://www.versioneye.com/objective-c/sdwebimage/3.3/badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/3.3)
[![Reference Status](https://www.versioneye.com/objective-c/sdwebimage/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/references) [![Reference Status](https://www.versioneye.com/objective-c/sdwebimage/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/references)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/rs/SDWebImage) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/rs/SDWebImage)
[![codecov](https://codecov.io/gh/rs/SDWebImage/branch/master/graph/badge.svg)](https://codecov.io/gh/rs/SDWebImage)
This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like `UIImageView`, `UIButton`, `MKAnnotationView`. This library provides a category for UIImageView with support for remote images coming from the web.
## Features It provides:
- [x] Categories for `UIImageView`, `UIButton`, `MKAnnotationView` adding web image and cache management - An `UIImageView` category adding web image and cache management to the Cocoa Touch framework
- [x] An asynchronous image downloader - An asynchronous image downloader
- [x] An asynchronous memory + disk image caching with automatic cache expiration handling - An asynchronous memory + disk image caching with automatic cache expiration handling
- [x] A background image decompression - Animated GIF support
- [x] A guarantee that the same URL won't be downloaded several times - WebP format support
- [x] A guarantee that bogus URLs won't be retried again and again - A background image decompression
- [x] A guarantee that main thread will never be blocked - A guarantee that the same URL won't be downloaded several times
- [x] Performances! - A guarantee that bogus URLs won't be retried again and again
- [x] Use GCD and ARC - A guarantee that main thread will never be blocked
- Performances!
- Use GCD and ARC
- Arm64 support
## Supported Image Formats NOTE: Version 3.8 of SDWebImage requires iOS 7 or later (because of NSURLSession).
Versions 3.7 to 3.0 requires iOS 5.1.1. If you need iOS < 5.0 support, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat).
- Image formats supported by UIImage (JPEG, PNG, ...), including GIF [How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F)
- WebP format, including animated WebP (use the `WebP` subspec)
## Requirements Who Uses It
----------
- iOS 7.0 or later Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list.
- tvOS 9.0 or later
- watchOS 2.0 or later
- macOS 10.9 or later
- Xcode 7.3 or later
#### Backwards compatibility How To Use
----------
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/rs/SDWebImage/tree/3.7.6) API documentation is available at [CocoaDocs - SDWebImage](http://cocoadocs.org/docsets/SDWebImage/)
- For iOS < 5.0, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat).
## Getting Started ### Using UIImageView+WebCache category with UITableView
- Read this Readme doc Just #import the UIImageView+WebCache.h header, and call the sd_setImageWithURL:placeholderImage:
- Read the [How to use section](https://github.com/rs/SDWebImage#how-to-use) method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
- Read the [Documentation @ CocoaDocs](http://cocoadocs.org/docsets/SDWebImage/) handled for you, from async downloads to caching management.
- Try the example by downloading the project from Github or even easier using CocoaPods try `pod try SDWebImage`
- Read the [Installation Guide](https://github.com/rs/SDWebImage/wiki/Installation-Guide)
- Read the [SDWebImage 4.0 Migration Guide](Docs/SDWebImage-4.0-Migration-guide.md) to get an idea of the changes from 3.x to 4.x
- Read the [Common Problems](https://github.com/rs/SDWebImage/wiki/Common-Problems) to find the solution for common problems
- Go to the [Wiki Page](https://github.com/rs/SDWebImage/wiki) for more information such as [Advanced Usage](https://github.com/rs/SDWebImage/wiki/Advanced-Usage)
## Who Uses It ```objective-c
- Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list. #import <SDWebImage/UIImageView+WebCache.h>
## Communication ...
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage). (Tag 'sdwebimage') - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage). static NSString *MyIdentifier = @"MyIdentifier";
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## How To Use UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
* Objective-C // Here we use the new provided sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
cell.textLabel.text = @"My Text";
return cell;
}
```
### Using blocks
With blocks, you can be notified about the image download progress and whenever the image retrieval
has completed with success or not:
```objective-c ```objective-c
#import <SDWebImage/UIImageView+WebCache.h> // Here we use the new provided sd_setImageWithURL: method to load the web image
... [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
... completion code here ...
}];
``` ```
* Swift Note: neither your success nor failure block will be call if your image request is canceled before completion.
```swift ### Using SDWebImageManager
import SDWebImage
The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the
asynchronous downloader with the image cache store. You can use this class directly to benefit
from web image downloading with caching in another context than a UIView (ie: with Cocoa).
Here is a simple example of how to use SDWebImageManager:
```objective-c
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
```
### Using Asynchronous Image Downloader Independently
It's also possible to use the async image downloader independently:
imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png")) ```objective-c
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
}
}];
``` ```
- For details about how to use the library and clear examples, see [The detailed How to use](Docs/HowToUse.md) ### Using Asynchronous Image Caching Independently
It is also possible to use the async based image cache store independently. SDImageCache
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
asynchronous so it doesn't add unnecessary latency to the UI.
The SDImageCache class provides a singleton instance for convenience but you can create your own
instance if you want to create separated cache namespace.
To lookup the cache, you use the `queryDiskCacheForKey:done:` method. If the method returns nil, it means the cache
doesn't currently own the image. You are thus responsible for generating and caching it. The cache
key is an application unique identifier for the image to cache. It is generally the absolute URL of
the image.
```objective-c
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
// image is not nil if image was found
}];
```
By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache.
You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`.
To store an image into the cache, you use the storeImage:forKey: method:
```objective-c
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
```
By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If
you want only the memory cache, use the alternative method storeImage:forKey:toDisk: with a negative
third argument.
### Using cache key filter
Sometime, you may not want to use the image URL as cache key because part of the URL is dynamic
(i.e.: for access control purpose). SDWebImageManager provides a way to set a cache key filter that
takes the NSURL as input, and output a cache key NSString.
The following example sets a filter in the application delegate that will remove any query-string from
the URL before to use it as a cache key:
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
};
// Your app init code...
return YES;
}
```
Common Problems
---------------
### Using dynamic image size with UITableViewCell
## Animated Images (GIF) support UITableView determines the size of the image by the first image set for a cell. If your remote images
don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue.
The following article gives a way to workaround this issue:
- Starting with the 4.0 version, we rely on [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) to take care of our animated images. [http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/)
- If you use cocoapods, add `pod 'SDWebImage/GIF'` to your podfile.
- To use it, simply make sure you use `FLAnimatedImageView` instead of `UIImageView`.
- **Note**: there is a backwards compatible feature, so if you are still trying to load a GIF into a `UIImageView`, it will only show the 1st frame as a static image by default. However, you can enable the full GIF support by using the built-in GIF coder. See [GIF coder](https://github.com/rs/SDWebImage/wiki/Advanced-Usage#gif-coder)
- **Important**: FLAnimatedImage only works on the iOS platform. For macOS, use `NSImageView` with `animates` set to `YES` to show the entire animated images and `NO` to only show the 1st frame. For all the other platforms (tvOS, watchOS) we will fallback to the backwards compatibility feature described above
## Installation
### Handle image refresh
SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly.
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the `SDWebImageRefreshCached` flag. This will slightly degrade the performance but will respect the HTTP caching control headers:
``` objective-c
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"]
placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"]
options:SDWebImageRefreshCached];
```
### Add a progress indicator
See this category: https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage
Installation
------------
There are three ways to use SDWebImage in your project: There are three ways to use SDWebImage in your project:
- using CocoaPods - using CocoaPods
- using Carthage - copying all the files into your project
- by cloning the project into your repository - importing the project as a static library
### Installation with CocoaPods ### Installation with CocoaPods
...@@ -109,7 +224,7 @@ There are three ways to use SDWebImage in your project: ...@@ -109,7 +224,7 @@ There are three ways to use SDWebImage in your project:
#### Podfile #### Podfile
``` ```
platform :ios, '7.0' platform :ios, '7.0'
pod 'SDWebImage', '~> 4.0' pod 'SDWebImage', '~>3.8'
``` ```
If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+: If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
...@@ -120,7 +235,7 @@ use_frameworks! ...@@ -120,7 +235,7 @@ use_frameworks!
#### Subspecs #### Subspecs
There are 4 subspecs available now: `Core`, `MapKit`, `GIF` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it). There are 3 subspecs available now: `Core`, `MapKit` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it).
Podfile example: Podfile example:
``` ```
...@@ -138,8 +253,63 @@ To install with carthage, follow the instruction on [Carthage](https://github.co ...@@ -138,8 +253,63 @@ To install with carthage, follow the instruction on [Carthage](https://github.co
github "rs/SDWebImage" github "rs/SDWebImage"
``` ```
#### Usage
Swift
If you installed using CocoaPods:
```
import SDWebImage
```
If you installed manually:
```
import WebImage
```
Objective-C
```
@import WebImage;
```
### Installation by cloning the repository ### Installation by cloning the repository
- see [Manual install](Docs/ManualInstallation.md)
In order to gain access to all the files from the repository, you should clone it.
```
git clone --recursive https://github.com/rs/SDWebImage.git
```
### Add the SDWebImage project to your project
- Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/releases)
- Right-click on the project navigator and select "Add Files to "Your Project":
- In the dialog, select SDWebImage.framework:
- Check the "Copy items into destination group's folder (if needed)" checkbox
### Add dependencies
- In you application project app’s target settings, find the "Build Phases" section and open the "Link Binary With Libraries" block:
- Click the "+" button again and select the "ImageIO.framework", this is needed by the progressive download feature:
### Add Linker Flag
Open the "Build Settings" tab, in the "Linking" section, locate the "Other Linker Flags" setting and add the "-ObjC" flag:
![Other Linker Flags](http://dl.dropbox.com/u/123346/SDWebImage/10_other_linker_flags.jpg)
Alternatively, if this causes compilation problems with frameworks that extend optional libraries, such as Parse, RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load SDWebImage.framework/Versions/Current/SDWebImage
```
If you're using Cocoa Pods and have any frameworks that extend optional libraries, such as Parsen RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load $(TARGET_BUILD_DIR)/libPods.a
```
and this:
```
$(inherited)
```
### Import headers in your source files ### Import headers in your source files
...@@ -154,26 +324,11 @@ In the source files where you need to use the library, import the header file: ...@@ -154,26 +324,11 @@ In the source files where you need to use the library, import the header file:
At this point your workspace should build without error. If you are having problem, post to the Issue and the At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it. community can help you solve it.
## Author Future Enhancements
- [Olivier Poitrey](https://github.com/rs) -------------------
## Collaborators - LRU memory cache cleanup instead of reset on memory warning
- [Konstantinos K.](https://github.com/mythodeia)
- [Bogdan Poplauschi](https://github.com/bpoplauschi)
- [Chester Liu](https://github.com/skyline75489)
- [DreamPiggy](https://github.com/dreampiggy)
- [Wu Zhong](https://github.com/zhongwuzw)
## Licenses ## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE). All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE).
## Architecture
<p align="center" >
<img src="Docs/SDWebImageClassDiagram.png" title="SDWebImage class diagram">
</p>
<p align="center" >
<img src="Docs/SDWebImageSequenceDiagram.png" title="SDWebImage sequence diagram">
</p>
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_MAC
#import "SDWebImageManager.h"
@interface NSButton (WebCache)
#pragma mark - Image
/**
* Get the current image URL.
*/
- (nullable NSURL *)sd_currentImageURL;
/**
* Set the button `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Set the button `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the button `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the button `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the button `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the button `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the button `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Alternate Image
/**
* Get the current alternateImage URL.
*/
- (nullable NSURL *)sd_currentAlternateImageURL;
/**
* Set the button `alternateImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Set the button `alternateImage` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param placeholder The alternateImage to be set initially, until the alternateImage request finishes.
* @see sd_setAlternateImageWithURL:placeholderImage:options:
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the button `alternateImage` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param placeholder The alternateImage to be set initially, until the alternateImage request finishes.
* @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the button `alternateImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the alternateImage parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the alternateImage was retrieved from the local cache or from the network.
* The fourth parameter is the original alternateImage url.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the button `alternateImage` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param placeholder The alternateImage to be set initially, until the alternateImage request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the alternateImage parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the alternateImage was retrieved from the local cache or from the network.
* The fourth parameter is the original alternateImage url.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the button `alternateImage` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param placeholder The alternateImage to be set initially, until the alternateImage request finishes.
* @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the alternateImage parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the alternateImage was retrieved from the local cache or from the network.
* The fourth parameter is the original alternateImage url.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the button `alternateImage` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the alternateImage.
* @param placeholder The alternateImage to be set initially, until the alternateImage request finishes.
* @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while alternateImage is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the alternateImage parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the alternateImage was retrieved from the local cache or from the network.
* The fourth parameter is the original alternateImage url.
*/
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Cancel
/**
* Cancel the current image download
*/
- (void)sd_cancelCurrentImageLoad;
/**
* Cancel the current alternateImage download
*/
- (void)sd_cancelCurrentAlternateImageLoad;
@end
#endif
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "NSButton+WebCache.h"
#if SD_MAC
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
static inline NSString * imageOperationKey() {
return @"NSButtonImageOperation";
}
static inline NSString * alternateImageOperationKey() {
return @"NSButtonAlternateImageOperation";
}
@implementation NSButton (WebCache)
#pragma mark - Image
- (void)sd_setImageWithURL:(nullable NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
self.sd_currentImageURL = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:imageOperationKey()
setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) {
weakSelf.image = image;
}
progress:progressBlock
completed:completedBlock];
}
#pragma mark - Alternate Image
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url {
[self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
self.sd_currentAlternateImageURL = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:alternateImageOperationKey()
setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) {
weakSelf.alternateImage = image;
}
progress:progressBlock
completed:completedBlock];
}
#pragma mark - Cancel
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:imageOperationKey()];
}
- (void)sd_cancelCurrentAlternateImageLoad {
[self sd_cancelImageLoadOperationWithKey:alternateImageOperationKey()];
}
#pragma mar - Private
- (NSURL *)sd_currentImageURL {
return objc_getAssociatedObject(self, @selector(sd_currentImageURL));
}
- (void)setSd_currentImageURL:(NSURL *)sd_currentImageURL {
objc_setAssociatedObject(self, @selector(sd_currentImageURL), sd_currentImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSURL *)sd_currentAlternateImageURL {
return objc_getAssociatedObject(self, @selector(sd_currentAlternateImageURL));
}
- (void)setSd_currentAlternateImageURL:(NSURL *)sd_currentAlternateImageURL {
objc_setAssociatedObject(self, @selector(sd_currentAlternateImageURL), sd_currentAlternateImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#endif
/* //
* This file is part of the SDWebImage package. // Created by Fabrice Aneche on 06/01/14.
* (c) Olivier Poitrey <rs@dailymotion.com> // Copyright (c) 2014 Dailymotion. All rights reserved.
* (c) Fabrice Aneche //
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP,
SDImageFormatHEIC
};
@interface NSData (ImageContentType) @interface NSData (ImageContentType)
/** /**
* Return image format * Compute the content type for an image data
* *
* @param data the input image data * @param data the input data
* *
* @return the image format as `SDImageFormat` (enum) * @return the content type as string (i.e. image/jpeg, image/gif)
*/ */
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data; + (NSString *)sd_contentTypeForImageData:(NSData *)data;
/** @end
* Convert SDImageFormat to UTType
*
* @param format Format as SDImageFormat
* @return The UTType as CFStringRef
*/
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format;
/**
* Convert UTTyppe to SDImageFormat @interface NSData (ImageContentTypeDeprecated)
*
* @param uttype The UTType as CFStringRef + (NSString *)contentTypeForImageData:(NSData *)data __deprecated_msg("Use `sd_contentTypeForImageData:`");
* @return The Format as SDImageFormat
*/
+ (SDImageFormat)sd_imageFormatFromUTType:(nonnull CFStringRef)uttype;
@end @end
/* //
* This file is part of the SDWebImage package. // Created by Fabrice Aneche on 06/01/14.
* (c) Olivier Poitrey <rs@dailymotion.com> // Copyright (c) 2014 Dailymotion. All rights reserved.
* (c) Fabrice Aneche //
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "NSData+ImageContentType.h" #import "NSData+ImageContentType.h"
#if SD_MAC
#import <CoreServices/CoreServices.h>
#else
#import <MobileCoreServices/MobileCoreServices.h>
#endif
// Currently Image/IO does not support WebP
#define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp")
// AVFileTypeHEIC is defined in AVFoundation via iOS 11, we use this without import AVFoundation
#define kSDUTTypeHEIC ((__bridge CFStringRef)@"public.heic")
@implementation NSData (ImageContentType) @implementation NSData (ImageContentType)
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data { + (NSString *)sd_contentTypeForImageData:(NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
uint8_t c; uint8_t c;
[data getBytes:&c length:1]; [data getBytes:&c length:1];
switch (c) { switch (c) {
case 0xFF: case 0xFF:
return SDImageFormatJPEG; return @"image/jpeg";
case 0x89: case 0x89:
return SDImageFormatPNG; return @"image/png";
case 0x47: case 0x47:
return SDImageFormatGIF; return @"image/gif";
case 0x49: case 0x49:
case 0x4D: case 0x4D:
return SDImageFormatTIFF; return @"image/tiff";
case 0x52: { case 0x52:
if (data.length >= 12) { // R as RIFF for WEBP
//RIFF....WEBP if ([data length] < 12) {
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; return nil;
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
} }
break;
} NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
case 0x00: { if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
if (data.length >= 12) { return @"image/webp";
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
} }
break;
}
}
return SDImageFormatUndefined;
}
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format { return nil;
CFStringRef UTType;
switch (format) {
case SDImageFormatJPEG:
UTType = kUTTypeJPEG;
break;
case SDImageFormatPNG:
UTType = kUTTypePNG;
break;
case SDImageFormatGIF:
UTType = kUTTypeGIF;
break;
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
break;
case SDImageFormatHEIC:
UTType = kSDUTTypeHEIC;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;
break;
} }
return UTType; return nil;
} }
+ (SDImageFormat)sd_imageFormatFromUTType:(CFStringRef)uttype { @end
if (!uttype) {
return SDImageFormatUndefined;
} @implementation NSData (ImageContentTypeDeprecated)
SDImageFormat imageFormat;
if (CFStringCompare(uttype, kUTTypeJPEG, 0) == kCFCompareEqualTo) { + (NSString *)contentTypeForImageData:(NSData *)data {
imageFormat = SDImageFormatJPEG; return [self sd_contentTypeForImageData:data];
} else if (CFStringCompare(uttype, kUTTypePNG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPNG;
} else if (CFStringCompare(uttype, kUTTypeGIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatGIF;
} else if (CFStringCompare(uttype, kUTTypeTIFF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatTIFF;
} else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatWebP;
} else if (CFStringCompare(uttype, kSDUTTypeHEIC, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIC;
} else {
imageFormat = SDImageFormatUndefined;
}
return imageFormat;
} }
@end @end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_MAC
#import <Cocoa/Cocoa.h>
@interface NSImage (WebCache)
- (CGImageRef)CGImage;
- (NSArray<NSImage *> *)images;
- (BOOL)isGIF;
@end
#endif
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "NSImage+WebCache.h"
#if SD_MAC
@implementation NSImage (WebCache)
- (CGImageRef)CGImage {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
CGImageRef cgImage = [self CGImageForProposedRect:&imageRect context:NULL hints:nil];
return cgImage;
}
- (NSArray<NSImage *> *)images {
return nil;
}
- (BOOL)isGIF {
BOOL isGIF = NO;
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
NSUInteger frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
isGIF = frameCount > 1 ? YES : NO;
break;
}
}
return isGIF;
}
@end
#endif
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_MAC
// A subclass of `NSBitmapImageRep` to fix that GIF loop count issue because `NSBitmapImageRep` will reset `NSImageCurrentFrameDuration` by using `kCGImagePropertyGIFDelayTime` but not `kCGImagePropertyGIFUnclampedDelayTime`.
// Built in GIF coder use this instead of `NSBitmapImageRep` for better GIF rendering. If you do not want this, only enable `SDWebImageImageIOCoder`, which just call `NSImage` API and actually use `NSBitmapImageRep` for GIF image.
@interface SDAnimatedImageRep : NSBitmapImageRep
@end
#endif
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDAnimatedImageRep.h"
#if SD_MAC
#import "SDWebImageGIFCoder.h"
@interface SDWebImageGIFCoder ()
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source;
@end
@interface SDAnimatedImageRep ()
@property (nonatomic, assign, readonly, nullable) CGImageSourceRef imageSource;
@end
@implementation SDAnimatedImageRep
// `NSBitmapImageRep` will use `kCGImagePropertyGIFDelayTime` whenever you call `setProperty:withValue:` with `NSImageCurrentFrame` to change the current frame. We override it and use the actual `kCGImagePropertyGIFUnclampedDelayTime` if need.
- (void)setProperty:(NSBitmapImageRepPropertyKey)property withValue:(id)value {
[super setProperty:property withValue:value];
if ([property isEqualToString:NSImageCurrentFrame]) {
// Access the image source
CGImageSourceRef imageSource = self.imageSource;
if (!imageSource) {
return;
}
// Check format type
CFStringRef type = CGImageSourceGetType(imageSource);
if (!type) {
return;
}
NSUInteger index = [value unsignedIntegerValue];
float frameDuration = 0;
// Through we currently process GIF only, in the 5.x we support APNG so we keep the extensibility
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) {
frameDuration = [[SDWebImageGIFCoder sharedCoder] sd_frameDurationAtIndex:index source:imageSource];
}
if (!frameDuration) {
return;
}
// Reset super frame duration with the actual frame duration
[super setProperty:NSImageCurrentFrameDuration withValue:@(frameDuration)];
}
}
- (CGImageSourceRef)imageSource {
if (_tiffData) {
return (__bridge CGImageSourceRef)(_tiffData);
}
return NULL;
}
@end
#endif
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDImageCacheConfig.h"
typedef NS_ENUM(NSInteger, SDImageCacheType) { typedef NS_ENUM(NSInteger, SDImageCacheType) {
/** /**
...@@ -25,41 +24,33 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) { ...@@ -25,41 +24,33 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeMemory SDImageCacheTypeMemory
}; };
typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);
/**
* By default, we do not query disk data when the image is cached in memory. This mask can force to query disk data at the same time.
*/
SDImageCacheQueryDataWhenInMemory = 1 << 0,
/**
* By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously.
*/
SDImageCacheQueryDiskSync = 1 << 1,
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
*/
SDImageCacheScaleDownLargeImages = 1 << 2
};
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache); typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize); typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
/** /**
* SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
* asynchronous so it doesn’t add unnecessary latency to the UI. * asynchronous so it doesn’t add unnecessary latency to the UI.
*/ */
@interface SDImageCache : NSObject @interface SDImageCache : NSObject
#pragma mark - Properties /**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* disable iCloud backup [defaults to YES]
*/
@property (assign, nonatomic) BOOL shouldDisableiCloud;
/** /**
* Cache Config object - storing all kind of settings * use memory cache [defaults to YES]
*/ */
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config; @property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
/** /**
* The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory. * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
...@@ -71,21 +62,29 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -71,21 +62,29 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
*/ */
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit; @property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
#pragma mark - Singleton and initialization /**
* The maximum length of time to keep an image in the cache, in seconds
*/
@property (assign, nonatomic) NSInteger maxCacheAge;
/**
* The maximum size of the cache, in bytes.
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;
/** /**
* Returns global shared cache instance * Returns global shared cache instance
* *
* @return SDImageCache global instance * @return SDImageCache global instance
*/ */
+ (nonnull instancetype)sharedImageCache; + (SDImageCache *)sharedImageCache;
/** /**
* Init a new cache store with a specific namespace * Init a new cache store with a specific namespace
* *
* @param ns The namespace to use for this cache store * @param ns The namespace to use for this cache store
*/ */
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns; - (id)initWithNamespace:(NSString *)ns;
/** /**
* Init a new cache store with a specific namespace and directory * Init a new cache store with a specific namespace and directory
...@@ -93,12 +92,9 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -93,12 +92,9 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
* @param ns The namespace to use for this cache store * @param ns The namespace to use for this cache store
* @param directory Directory to cache disk images in * @param directory Directory to cache disk images in
*/ */
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
#pragma mark - Cache paths -(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
/** /**
* Add a read-only cache path to search for images pre-cached by SDImageCache * Add a read-only cache path to search for images pre-cached by SDImageCache
...@@ -106,151 +102,99 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -106,151 +102,99 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
* *
* @param path The path to use for this read-only cache path * @param path The path to use for this read-only cache path
*/ */
- (void)addReadOnlyCachePath:(nonnull NSString *)path; - (void)addReadOnlyCachePath:(NSString *)path;
#pragma mark - Store Ops
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/** /**
* Asynchronously store an image into memory and disk cache at the given key. * Store an image into memory and disk cache at the given key.
* *
* @param image The image to store * @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL * @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
* @param completionBlock A block executed after the operation is finished
*/ */
- (void)storeImage:(nullable UIImage *)image - (void)storeImage:(UIImage *)image forKey:(NSString *)key;
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/** /**
* Asynchronously store an image into memory and disk cache at the given key. * Store an image into memory and optionally disk cache at the given key.
* *
* @param image The image to store * @param image The image to store
* @param imageData The image data as returned by the server, this representation will be used for disk storage * @param key The unique image cache key, usually it's image absolute URL
* instead of converting the given image object into a storable/compressed image format in order * @param toDisk Store the image to disk cache if YES
* to save quality and CPU
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Synchronously store image NSData into disk cache at the given key.
*
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
*/ */
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key; - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
#pragma mark - Query and Retrieve Ops
/** /**
* Async check if image exists in disk cache already (does not load the image) * Store an image into memory and optionally disk cache at the given key.
* *
* @param key the key describing the url * @param image The image to store
* @param completionBlock the block to be executed when the check is done. * @param recalculate BOOL indicates if imageData can be used or a new data should be constructed from the UIImage
* @note the completion block will be always executed on the main queue * @param imageData The image data as returned by the server, this representation will be used for disk storage
* instead of converting the given image object into a storable/compressed image format in order
* to save quality and CPU
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
*/ */
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
/** /**
* Sync check if image data exists in disk cache already (does not load the image) * Store image NSData into disk cache at the given key.
* *
* @param key the key describing the url * @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
*/ */
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key; - (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key;
/** /**
* Query the image data for the given key synchronously. * Query the disk cache asynchronously.
* *
* @param key The unique key used to store the wanted image * @param key The unique key used to store the wanted image
* @return The image data for the given key, or nil if not found.
*/ */
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key; - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
/** /**
* Operation that queries the cache asynchronously and call the completion when done. * Query the memory cache synchronously.
*
* @param key The unique key used to store the wanted image
* @param doneBlock The completion block. Will not get called if the operation is cancelled
* *
* @return a NSOperation instance containing the cache op * @param key The unique key used to store the wanted image
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock; - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
/** /**
* Operation that queries the cache asynchronously and call the completion when done. * Query the disk cache synchronously after checking the memory cache.
* *
* @param key The unique key used to store the wanted image * @param key The unique key used to store the wanted image
* @param options A mask to specify options to use for this cache query
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock; - (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
/** /**
* Query the memory cache synchronously. * Remove the image from memory and disk cache asynchronously
* *
* @param key The unique key used to store the image * @param key The unique image cache key
* @return The image for the given key, or nil if not found.
*/ */
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key; - (void)removeImageForKey:(NSString *)key;
/**
* Query the disk cache synchronously.
*
* @param key The unique key used to store the image
* @return The image for the given key, or nil if not found.
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
/** /**
* Query the cache (memory and or disk) synchronously after checking the memory cache. * Remove the image from memory and disk cache asynchronously
* *
* @param key The unique key used to store the image * @param key The unique image cache key
* @return The image for the given key, or nil if not found. * @param completion An block that should be executed after the image has been removed (optional)
*/ */
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key; - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
#pragma mark - Remove Ops
/** /**
* Remove the image from memory and disk cache asynchronously * Remove the image from memory and optionally disk cache asynchronously
* *
* @param key The unique image cache key * @param key The unique image cache key
* @param completion A block that should be executed after the image has been removed (optional) * @param fromDisk Also remove cache entry from disk if YES
*/ */
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion; - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
/** /**
* Remove the image from memory and optionally disk cache asynchronously * Remove the image from memory and optionally disk cache asynchronously
* *
* @param key The unique image cache key * @param key The unique image cache key
* @param fromDisk Also remove cache entry from disk if YES * @param fromDisk Also remove cache entry from disk if YES
* @param completion A block that should be executed after the image has been removed (optional) * @param completion An block that should be executed after the image has been removed (optional)
*/ */
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion; - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
#pragma mark - Cache clean Ops
/** /**
* Clear all memory cached images * Clear all memory cached images
...@@ -258,18 +202,28 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -258,18 +202,28 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
- (void)clearMemory; - (void)clearMemory;
/** /**
* Async clear all disk cached images. Non-blocking method - returns immediately. * Clear all disk cached images. Non-blocking method - returns immediately.
* @param completion A block that should be executed after cache expiration completes (optional) * @param completion An block that should be executed after cache expiration completes (optional)
*/
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
/**
* Clear all disk cached images
* @see clearDiskOnCompletion:
*/ */
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion; - (void)clearDisk;
/** /**
* Async remove all expired cached image from disk. Non-blocking method - returns immediately. * Remove all expired cached image from disk. Non-blocking method - returns immediately.
* @param completionBlock A block that should be executed after cache expiration completes (optional) * @param completionBlock An block that should be executed after cache expiration completes (optional)
*/ */
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock; - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
#pragma mark - Cache Info /**
* Remove all expired cached image from disk
* @see cleanDiskWithCompletionBlock:
*/
- (void)cleanDisk;
/** /**
* Get the size used by the disk cache * Get the size used by the disk cache
...@@ -284,9 +238,25 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -284,9 +238,25 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
/** /**
* Asynchronously calculate the disk cache's size. * Asynchronously calculate the disk cache's size.
*/ */
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock; - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
/**
* Async check if image exists in disk cache already (does not load the image)
*
* @param key the key describing the url
* @param completionBlock the block to be executed when the check is done.
* @note the completion block will be always executed on the main queue
*/
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
#pragma mark - Cache Paths /**
* Check if image exists in disk cache already (does not load the image)
*
* @param key the key describing the url
*
* @return YES if an image exists for the given key
*/
- (BOOL)diskImageExistsWithKey:(NSString *)key;
/** /**
* Get the cache path for a certain key (needs the cache path root folder) * Get the cache path for a certain key (needs the cache path root folder)
...@@ -296,7 +266,7 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -296,7 +266,7 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
* *
* @return the cache path * @return the cache path
*/ */
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path; - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
/** /**
* Get the default cache path for a certain key * Get the default cache path for a certain key
...@@ -305,6 +275,6 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot ...@@ -305,6 +275,6 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
* *
* @return the default cache path * @return the default cache path
*/ */
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key; - (NSString *)defaultCachePathForKey:(NSString *)key;
@end @end
...@@ -7,158 +7,70 @@ ...@@ -7,158 +7,70 @@
*/ */
#import "SDImageCache.h" #import "SDImageCache.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonDigest.h>
#import "NSImage+WebCache.h"
#import "SDWebImageCodersManager.h"
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
// A memory cache which auto purge the cache on memory warning and support weak cache.
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
@interface AutoPurgeCache : NSCache
@end @end
// Private @implementation AutoPurgeCache
@interface SDMemoryCache <KeyType, ObjectType> ()
@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;
@end
@implementation SDMemoryCache
// Current this seems no use on macOS (macOS use virtual memory and do not clear cache when memory warning). So we only override on iOS/tvOS platform.
// But in the future there may be more options and features for this subclass.
#if SD_UIKIT
- (void)dealloc { - (id)init
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; {
}
- (instancetype)initWithConfig:(SDImageCacheConfig *)config {
self = [super init]; self = [super init];
if (self) { if (self) {
// Use a strong-weak maptable storing the secondary cache. Follow the doc that NSCache does not copy keys [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
// This is useful when the memory warning, the cache was purged. However, the image instance can be retained by other instance such as imageViews and alive.
// At this case, we can sync weak cache back and do not need to load from disk cache
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
self.config = config;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
} }
return self; return self;
} }
- (void)didReceiveMemoryWarning:(NSNotification *)notification { - (void)dealloc
// Only remove cache, but keep weak cache {
[super removeAllObjects]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
// `setObject:forKey:` just call this with 0 cost. Override this is enough
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);
}
} }
- (id)objectForKey:(id)key { @end
id obj = [super objectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return obj;
}
if (key && !obj) {
// Check weak cache
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
cost = SDCacheCostForImage(obj);
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}
- (void)removeObjectForKey:(id)key { static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
[super removeObjectForKey:key]; // PNG signature bytes and data (below)
if (!self.config.shouldUseWeakMemoryCache) { static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
return; static NSData *kPNGSignatureData = nil;
}
if (key) {
// Remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);
}
}
- (void)removeAllObjects { BOOL ImageDataHasPNGPreffix(NSData *data);
[super removeAllObjects];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
// Manually remove should also remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
UNLOCK(self.weakCacheLock);
}
#else BOOL ImageDataHasPNGPreffix(NSData *data) {
NSUInteger pngSignatureLength = [kPNGSignatureData length];
if ([data length] >= pngSignatureLength) {
if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) {
return YES;
}
}
- (instancetype)initWithConfig:(SDImageCacheConfig *)config { return NO;
self = [super init];
return self;
} }
#endif FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return image.size.height * image.size.width * image.scale * image.scale;
@end }
@interface SDImageCache () @interface SDImageCache ()
#pragma mark - Properties @property (strong, nonatomic) NSCache *memCache;
@property (strong, nonatomic, nonnull) SDMemoryCache *memCache; @property (strong, nonatomic) NSString *diskCachePath;
@property (strong, nonatomic, nonnull) NSString *diskCachePath; @property (strong, nonatomic) NSMutableArray *customPaths;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths; @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
@property (strong, nonatomic, nonnull) NSFileManager *fileManager;
@end @end
@implementation SDImageCache @implementation SDImageCache {
NSFileManager *_fileManager;
#pragma mark - Singleton, init, dealloc }
+ (nonnull instancetype)sharedImageCache { + (SDImageCache *)sharedImageCache {
static dispatch_once_t once; static dispatch_once_t once;
static id instance; static id instance;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
...@@ -167,27 +79,30 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -167,27 +79,30 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return instance; return instance;
} }
- (instancetype)init { - (id)init {
return [self initWithNamespace:@"default"]; return [self initWithNamespace:@"default"];
} }
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns { - (id)initWithNamespace:(NSString *)ns {
NSString *path = [self makeDiskCachePath:ns]; NSString *path = [self makeDiskCachePath:ns];
return [self initWithNamespace:ns diskCacheDirectory:path]; return [self initWithNamespace:ns diskCacheDirectory:path];
} }
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) { if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]; NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// initialise PNG signature data
kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];
// Create IO serial queue // Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init]; // Init default values
_maxCacheAge = kDefaultCacheMaxCacheAge;
// Init the memory cache // Init the memory cache
_memCache = [[SDMemoryCache alloc] initWithConfig:_config]; _memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace; _memCache.name = fullNamespace;
// Init the disk cache // Init the disk cache
...@@ -198,19 +113,33 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -198,19 +113,33 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
_diskCachePath = path; _diskCachePath = path;
} }
// Set decompression to YES
_shouldDecompressImages = YES;
// memory cache enabled
_shouldCacheImagesInMemory = YES;
// Disable iCloud
_shouldDisableiCloud = YES;
dispatch_sync(_ioQueue, ^{ dispatch_sync(_ioQueue, ^{
self.fileManager = [NSFileManager new]; _fileManager = [NSFileManager new];
}); });
#if SD_UIKIT #if TARGET_OS_IOS
// Subscribe to app events // Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles) selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification name:UIApplicationWillTerminateNotification
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles) selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification name:UIApplicationDidEnterBackgroundNotification
object:nil]; object:nil];
#endif #endif
...@@ -221,11 +150,10 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -221,11 +150,10 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
- (void)dealloc { - (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
} }
#pragma mark - Cache paths - (void)addReadOnlyCachePath:(NSString *)path {
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) { if (!self.customPaths) {
self.customPaths = [NSMutableArray new]; self.customPaths = [NSMutableArray new];
} }
...@@ -235,114 +163,105 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -235,114 +163,105 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
} }
} }
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path { - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
NSString *filename = [self cachedFileNameForKey:key]; NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename]; return [path stringByAppendingPathComponent:filename];
} }
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key { - (NSString *)defaultCachePathForKey:(NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath]; return [self cachePathForKey:key inPath:self.diskCachePath];
} }
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key { #pragma mark SDImageCache (private)
const char *str = key.UTF8String;
- (NSString *)cachedFileNameForKey:(NSString *)key {
const char *str = [key UTF8String];
if (str == NULL) { if (str == NULL) {
str = ""; str = "";
} }
unsigned char r[CC_MD5_DIGEST_LENGTH]; unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r); CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]]; r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];
return filename;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace { return filename;
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
} }
#pragma mark - Store Ops #pragma mark ImageCache
- (void)storeImage:(nullable UIImage *)image // Init the disk cache
forKey:(nullable NSString *)key -(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
completion:(nullable SDWebImageNoParamsBlock)completionBlock { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock]; return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
} }
- (void)storeImage:(nullable UIImage *)image - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) { if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return; return;
} }
// if memory cache is enabled // if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) { if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image); NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost]; [self.memCache setObject:image forKey:key cost:cost];
} }
if (toDisk) { if (toDisk) {
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
@autoreleasepool { NSData *data = imageData;
NSData *data = imageData;
if (!data && image) { if (image && (recalculate || !data)) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format #if TARGET_OS_IPHONE
SDImageFormat format; // We need to determine if the image is a PNG or a JPEG
if (SDCGImageRefContainsAlpha(image.CGImage)) { // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
format = SDImageFormatPNG; // The first eight bytes of a PNG file always contain the following (decimal) values:
} else { // 137 80 78 71 13 10 26 10
format = SDImageFormatJPEG;
} // If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format]; // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;
// But if we have an image data, we will look at the preffix
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
} }
[self _storeImageDataToDisk:data forKey:key];
} if (imageIsPng) {
data = UIImagePNGRepresentation(image);
if (completionBlock) { }
dispatch_async(dispatch_get_main_queue(), ^{ else {
completionBlock(); data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}); }
#else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
} }
[self storeImageDataToDisk:data forKey:key];
}); });
} else {
if (completionBlock) {
completionBlock();
}
} }
} }
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key { - (void)storeImage:(UIImage *)image forKey:(NSString *)key {
if (!imageData || !key) { [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES];
return;
}
dispatch_sync(self.ioQueue, ^{
[self _storeImageDataToDisk:imageData forKey:key];
});
} }
// Make sure to call form io queue by caller - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk {
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key { [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk];
if (!imageData || !key) { }
- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key {
if (!imageData) {
return; return;
} }
if (![self.fileManager fileExistsAtPath:_diskCachePath]) { if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
} }
// get cache Path for image key // get cache Path for image key
...@@ -350,118 +269,95 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -350,118 +269,95 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
// transform to NSUrl // transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup // disable iCloud backup
if (self.config.shouldDisableiCloud) { if (self.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
} }
} }
#pragma mark - Query and Retrieve Ops - (BOOL)diskImageExistsWithKey:(NSString *)key {
BOOL exists = NO;
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
BOOL exists = [self _diskImageDataExistsWithKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key {
if (!key) {
return NO;
}
__block BOOL exists = NO;
dispatch_sync(self.ioQueue, ^{
exists = [self _diskImageDataExistsWithKey:key];
});
return exists; // this is an exception to access the filemanager on another queue than ioQueue, but we are using the shared instance
} // from apple docs on NSFileManager: The methods of the shared NSFileManager object can be called from multiple threads safely.
exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]];
// Make sure to call form io queue by caller
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
if (!key) {
return NO;
}
BOOL exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension // checking the key with and without the extension
if (!exists) { if (!exists) {
exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension]; exists = [[NSFileManager defaultManager] fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]];
} }
return exists; return exists;
} }
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key { - (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
if (!key) { dispatch_async(_ioQueue, ^{
return nil; BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
}
__block NSData *imageData = nil; // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
dispatch_sync(self.ioQueue, ^{ // checking the key with and without the extension
imageData = [self diskImageDataBySearchingAllPathsForKey:key]; if (!exists) {
exists = [_fileManager fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
}); });
return imageData;
} }
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key { - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key]; return [self.memCache objectForKey:key];
} }
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key { - (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// First check the in-memory cache... // First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key]; UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) { if (image) {
return image; return image;
} }
// Second check the disk cache... // Second check the disk cache...
image = [self imageFromDiskCacheForKey:key]; UIImage *diskImage = [self diskImageForKey:key];
return image; if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
} }
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key { - (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key {
NSString *defaultPath = [self defaultCachePathForKey:key]; NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil]; NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) { if (data) {
return data; return data;
} }
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension // checking the key with and without the extension
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil]; data = [NSData dataWithContentsOfFile:[defaultPath stringByDeletingPathExtension]];
if (data) { if (data) {
return data; return data;
} }
NSArray<NSString *> *customPaths = [self.customPaths copy]; NSArray *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) { for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path]; NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil]; NSData *imageData = [NSData dataWithContentsOfFile:filePath];
if (imageData) { if (imageData) {
return imageData; return imageData;
} }
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension // checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil]; imageData = [NSData dataWithContentsOfFile:[filePath stringByDeletingPathExtension]];
if (imageData) { if (imageData) {
return imageData; return imageData;
} }
...@@ -470,118 +366,89 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -470,118 +366,89 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return nil; return nil;
} }
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key { - (UIImage *)diskImageForKey:(NSString *)key {
NSData *data = [self diskImageDataForKey:key]; NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
return [self diskImageForKey:key data:data];
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data {
return [self diskImageForKey:key data:data options:0];
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options {
if (data) { if (data) {
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data]; UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image]; image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) { if (self.shouldDecompressImages) {
BOOL shouldScaleDown = options & SDImageCacheScaleDownLargeImages; image = [UIImage decodedImageWithImage:image];
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
} }
return image; return image;
} else { }
else {
return nil; return nil;
} }
} }
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image { - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image); return SDScaledImageForKey(key, image);
} }
- (NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDCacheQueryCompletedBlock)doneBlock { - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
return [self queryCacheOperationForKey:key options:0 done:doneBlock]; if (!doneBlock) {
} return nil;
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) { if (!key) {
if (doneBlock) { doneBlock(nil, SDImageCacheTypeNone);
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil; return nil;
} }
// First check the in-memory cache... // First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key]; UIImage *image = [self imageFromMemoryCacheForKey:key];
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory)); if (image) {
if (shouldQueryMemoryOnly) { doneBlock(image, SDImageCacheTypeMemory);
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil; return nil;
} }
NSOperation *operation = [NSOperation new]; NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{ dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) { if (operation.isCancelled) {
// do not call the completion if cancelled
return; return;
} }
@autoreleasepool { @autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key]; UIImage *diskImage = [self diskImageForKey:key];
UIImage *diskImage; if (diskImage && self.shouldCacheImagesInMemory) {
SDImageCacheType cacheType = SDImageCacheTypeDisk; NSUInteger cost = SDCacheCostForImage(diskImage);
if (image) { [self.memCache setObject:diskImage forKey:key cost:cost];
// the image is from in-memory cache
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
} }
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
} }
}; });
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation; return operation;
} }
#pragma mark - Remove Ops - (void)removeImageForKey:(NSString *)key {
[self removeImageForKey:key withCompletion:nil];
}
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion { - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion]; [self removeImageForKey:key fromDisk:YES withCompletion:completion];
} }
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion { - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk {
[self removeImageForKey:key fromDisk:fromDisk withCompletion:nil];
}
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
if (key == nil) { if (key == nil) {
return; return;
} }
if (self.config.shouldCacheImagesInMemory) { if (self.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key]; [self.memCache removeObjectForKey:key];
} }
if (fromDisk) { if (fromDisk) {
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
[self.fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil]; [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
...@@ -595,8 +462,6 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -595,8 +462,6 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
} }
# pragma mark - Mem Cache settings
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost { - (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost; self.memCache.totalCostLimit = maxMemoryCost;
} }
...@@ -613,16 +478,19 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -613,16 +478,19 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
self.memCache.countLimit = maxCountLimit; self.memCache.countLimit = maxCountLimit;
} }
#pragma mark - Cache clean Ops
- (void)clearMemory { - (void)clearMemory {
[self.memCache removeAllObjects]; [self.memCache removeAllObjects];
} }
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion { - (void)clearDisk {
[self clearDiskOnCompletion:nil];
}
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
[self.fileManager removeItemAtPath:self.diskCachePath error:nil]; [_fileManager removeItemAtPath:self.diskCachePath error:nil];
[self.fileManager createDirectoryAtPath:self.diskCachePath [_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES withIntermediateDirectories:YES
attributes:nil attributes:nil
error:NULL]; error:NULL];
...@@ -635,90 +503,73 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -635,90 +503,73 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
}); });
} }
- (void)deleteOldFiles { - (void)cleanDisk {
[self deleteOldFilesWithCompletionBlock:nil]; [self cleanDiskWithCompletionBlock:nil];
} }
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock { - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// Compute content date key to be used for tests
NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
switch (self.config.diskCacheExpireType) {
case SDImageCacheConfigExpireTypeAccessDate:
cacheContentDateKey = NSURLContentAccessDateKey;
break;
case SDImageCacheConfigExpireTypeModificationDate:
cacheContentDateKey = NSURLContentModificationDateKey;
break;
default:
break;
}
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files. // This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL]; errorHandler:NULL];
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary]; NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0; NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes: // Enumerate all of the files in the cache directory. This loop has two purposes:
// //
// 1. Removing files that are older than the expiration date. // 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass. // 2. Storing file attributes for the size-based cleanup pass.
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init]; NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) { for (NSURL *fileURL in fileEnumerator) {
NSError *error; NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors. // Skip directories.
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) { if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue; continue;
} }
// Remove files that are older than the expiration date; // Remove files that are older than the expiration date;
NSDate *modifiedDate = resourceValues[cacheContentDateKey]; NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) { if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL]; [urlsToDelete addObject:fileURL];
continue; continue;
} }
// Store a reference to this file and account for its total size. // Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue; currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
cacheFiles[fileURL] = resourceValues; [cacheFiles setObject:resourceValues forKey:fileURL];
} }
for (NSURL *fileURL in urlsToDelete) { for (NSURL *fileURL in urlsToDelete) {
[self.fileManager removeItemAtURL:fileURL error:nil]; [_fileManager removeItemAtURL:fileURL error:nil];
} }
// If our remaining disk cache exceeds a configured maximum size, perform a second // If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first. // size-based cleanup pass. We delete the oldest files first.
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) { if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass. // Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2; const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first). // Sort the remaining cache files by their last modification time (oldest first).
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) { usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}]; }];
// Delete files until we fall below our desired cache size. // Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) { for (NSURL *fileURL in sortedFiles) {
if ([self.fileManager removeItemAtURL:fileURL error:nil]) { if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL]; NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue; currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
if (currentCacheSize < desiredCacheSize) { if (currentCacheSize < desiredCacheSize) {
break; break;
...@@ -734,8 +585,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -734,8 +585,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
}); });
} }
#if SD_UIKIT - (void)backgroundCleanDisk {
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication"); Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return; return;
...@@ -749,22 +599,19 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -749,22 +599,19 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
}]; }];
// Start the long-running task and return immediately. // Start the long-running task and return immediately.
[self deleteOldFilesWithCompletionBlock:^{ [self cleanDiskWithCompletionBlock:^{
[application endBackgroundTask:bgTask]; [application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid; bgTask = UIBackgroundTaskInvalid;
}]; }];
} }
#endif
#pragma mark - Cache Info
- (NSUInteger)getSize { - (NSUInteger)getSize {
__block NSUInteger size = 0; __block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{ dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath]; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) { for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName]; NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil]; NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize]; size += [attrs fileSize];
} }
}); });
...@@ -774,20 +621,20 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -774,20 +621,20 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
- (NSUInteger)getDiskCount { - (NSUInteger)getDiskCount {
__block NSUInteger count = 0; __block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{ dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath]; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count; count = [[fileEnumerator allObjects] count];
}); });
return count; return count;
} }
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock { - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0; NSUInteger fileCount = 0;
NSUInteger totalSize = 0; NSUInteger totalSize = 0;
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize] includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL]; errorHandler:NULL];
...@@ -795,7 +642,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -795,7 +642,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
for (NSURL *fileURL in fileEnumerator) { for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize; NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]; [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += fileSize.unsignedIntegerValue; totalSize += [fileSize unsignedIntegerValue];
fileCount += 1; fileCount += 1;
} }
...@@ -808,4 +655,3 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { ...@@ -808,4 +655,3 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
} }
@end @end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
/**
* When the image is accessed it will update this value
*/
SDImageCacheConfigExpireTypeAccessDate,
/**
* The image was obtained from the disk cache (Default)
*/
SDImageCacheConfigExpireTypeModificationDate
};
@interface SDImageCacheConfig : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* Whether or not to disable iCloud backup
* Defaults to YES.
*/
@property (assign, nonatomic) BOOL shouldDisableiCloud;
/**
* Whether or not to use memory cache
* @note When the memory cache is disabled, the weak memory cache will also be disabled.
* Defaults to YES.
*/
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
/**
* The option to control weak memory cache for images. When enable, `SDImageCache`'s memory cache will use a weak maptable to store the image at the same time when it stored to memory, and get removed at the same time.
* However when memory warning is triggered, since the weak maptable does not hold a strong reference to image instacnce, even when the memory cache itself is purged, some images which are held strongly by UIImageViews or other live instances can be recovered again, to avoid later re-query from disk cache or network. This may be helpful for the case, for example, when app enter background and memory is purged, cause cell flashing after re-enter foreground.
* Defautls to YES. You can change this option dynamically.
*/
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;
/**
* The reading options while reading cache from disk.
* Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance.
*/
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;
/**
* The writing options while writing cache to disk.
* Defaults to `NSDataWritingAtomic`. You can set this to `NSDataWritingWithoutOverwriting` to prevent overwriting an existing file.
*/
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;
/**
* The maximum length of time to keep an image in the cache, in seconds.
*/
@property (assign, nonatomic) NSInteger maxCacheAge;
/**
* The maximum size of the cache, in bytes.
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;
/**
* The attribute which the clear cache will be checked against when clearing the disk cache
* Default is Modified Date
*/
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDImageCacheConfig.h"
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
@implementation SDImageCacheConfig
- (instancetype)init {
if (self = [super init]) {
_shouldDecompressImages = YES;
_shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES;
_shouldUseWeakMemoryCache = YES;
_diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic;
_maxCacheAge = kDefaultCacheMaxCacheAge;
_maxCacheSize = 0;
_diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;
}
return self;
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
#import "NSData+ImageContentType.h"
/**
A Boolean value indicating whether to scale down large images during decompressing. (NSNumber)
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageCoderScaleDownLargeImagesKey;
/**
Return the shared device-dependent RGB color space created with CGColorSpaceCreateDeviceRGB.
@return The device-dependent RGB color space
*/
CG_EXTERN CGColorSpaceRef _Nonnull SDCGColorSpaceGetDeviceRGB(void);
/**
Check whether CGImageRef contains alpha channel.
@param imageRef The CGImageRef
@return Return YES if CGImageRef contains alpha channel, otherwise return NO
*/
CG_EXTERN BOOL SDCGImageRefContainsAlpha(_Nullable CGImageRef imageRef);
/**
This is the image coder protocol to provide custom image decoding/encoding.
These methods are all required to implement.
@note Pay attention that these methods are not called from main queue.
*/
@protocol SDWebImageCoder <NSObject>
@required
#pragma mark - Decoding
/**
Returns YES if this coder can decode some data. Otherwise, the data should be passed to another coder.
@param data The image data so we can look at it
@return YES if this coder can decode the data, NO otherwise
*/
- (BOOL)canDecodeFromData:(nullable NSData *)data;
/**
Decode the image data to image.
@param data The image data to be decoded
@return The decoded image from data
*/
- (nullable UIImage *)decodedImageWithData:(nullable NSData *)data;
/**
Decompress the image with original image and image data.
@param image The original image to be decompressed
@param data The pointer to original image data. The pointer itself is nonnull but image data can be null. This data will set to cache if needed. If you do not need to modify data at the sametime, ignore this param.
@param optionsDict A dictionary containing any decompressing options. Pass {SDWebImageCoderScaleDownLargeImagesKey: @(YES)} to scale down large images
@return The decompressed image
*/
- (nullable UIImage *)decompressedImageWithImage:(nullable UIImage *)image
data:(NSData * _Nullable * _Nonnull)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict;
#pragma mark - Encoding
/**
Returns YES if this coder can encode some image. Otherwise, it should be passed to another coder.
@param format The image format
@return YES if this coder can encode the image, NO otherwise
*/
- (BOOL)canEncodeToFormat:(SDImageFormat)format;
/**
Encode the image to image data.
@param image The image to be encoded
@param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible
@return The encoded image data
*/
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format;
@end
/**
This is the image coder protocol to provide custom progressive image decoding.
These methods are all required to implement.
@note Pay attention that these methods are not called from main queue.
*/
@protocol SDWebImageProgressiveCoder <SDWebImageCoder>
@required
/**
Returns YES if this coder can incremental decode some data. Otherwise, it should be passed to another coder.
@param data The image data so we can look at it
@return YES if this coder can decode the data, NO otherwise
*/
- (BOOL)canIncrementallyDecodeFromData:(nullable NSData *)data;
/**
Incremental decode the image data to image.
@param data The image data has been downloaded so far
@param finished Whether the download has finished
@warning because incremental decoding need to keep the decoded context, we will alloc a new instance with the same class for each download operation to avoid conflicts
@return The decoded image from data
*/
- (nullable UIImage *)incrementallyDecodedImageWithData:(nullable NSData *)data finished:(BOOL)finished;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCoder.h"
NSString * const SDWebImageCoderScaleDownLargeImagesKey = @"scaleDownLargeImages";
CGColorSpaceRef SDCGColorSpaceGetDeviceRGB(void) {
static CGColorSpaceRef colorSpace;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colorSpace = CGColorSpaceCreateDeviceRGB();
});
return colorSpace;
}
BOOL SDCGImageRefContainsAlpha(CGImageRef imageRef) {
if (!imageRef) {
return NO;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
return hasAlpha;
}
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
#import "SDWebImageFrame.h"
@interface SDWebImageCoderHelper : NSObject
/**
Return an animated image with frames array.
For UIKit, this will apply the patch and then create animated UIImage. The patch is because that `+[UIImage animatedImageWithImages:duration:]` just use the average of duration for each image. So it will not work if different frame has different duration. Therefore we repeat the specify frame for specify times to let it work.
For AppKit, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not)
@param frames The frames array. If no frames or frames is empty, return nil
@return A animated image for rendering on UIImageView(UIKit) or NSImageView(AppKit)
*/
+ (UIImage * _Nullable)animatedImageWithFrames:(NSArray<SDWebImageFrame *> * _Nullable)frames;
/**
Return frames array from an animated image.
For UIKit, this will unapply the patch for the description above and then create frames array. This will also work for normal animated UIImage.
For AppKit, NSImage does not support animates other than GIF. This will try to decode the GIF imageRep and then create frames array.
@param animatedImage A animated image. If it's not animated, return nil
@return The frames array
*/
+ (NSArray<SDWebImageFrame *> * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage;
#if SD_UIKIT || SD_WATCH
/**
Convert an EXIF image orientation to an iOS one.
@param exifOrientation EXIF orientation
@return iOS orientation
*/
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation;
/**
Convert an iOS orientation to an EXIF image orientation.
@param imageOrientation iOS orientation
@return EXIF orientation
*/
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation;
#endif
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCoderHelper.h"
#import "SDWebImageFrame.h"
#import "UIImage+MultiFormat.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "SDAnimatedImageRep.h"
@implementation SDWebImageCoderHelper
+ (UIImage *)animatedImageWithFrames:(NSArray<SDWebImageFrame *> *)frames {
NSUInteger frameCount = frames.count;
if (frameCount == 0) {
return nil;
}
UIImage *animatedImage;
#if SD_UIKIT || SD_WATCH
NSUInteger durations[frameCount];
for (size_t i = 0; i < frameCount; i++) {
durations[i] = frames[i].duration * 1000;
}
NSUInteger const gcd = gcdArray(frameCount, durations);
__block NSUInteger totalDuration = 0;
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
[frames enumerateObjectsUsingBlock:^(SDWebImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
UIImage *image = frame.image;
NSUInteger duration = frame.duration * 1000;
totalDuration += duration;
NSUInteger repeatCount;
if (gcd) {
repeatCount = duration / gcd;
} else {
repeatCount = 1;
}
for (size_t i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
}];
animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f];
#else
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
// Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
CFRelease(imageDestination);
return nil;
}
CFRelease(imageDestination);
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
[animatedImage addRepresentation:imageRep];
#endif
return animatedImage;
}
+ (NSArray<SDWebImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
if (!animatedImage) {
return nil;
}
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
NSUInteger frameCount = 0;
#if SD_UIKIT || SD_WATCH
NSArray<UIImage *> *animatedImages = animatedImage.images;
frameCount = animatedImages.count;
if (frameCount == 0) {
return nil;
}
NSTimeInterval avgDuration = animatedImage.duration / frameCount;
if (avgDuration == 0) {
avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
}
__block NSUInteger index = 0;
__block NSUInteger repeatCount = 1;
__block UIImage *previousImage = animatedImages.firstObject;
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
// ignore first
if (idx == 0) {
return;
}
if ([image isEqual:previousImage]) {
repeatCount++;
} else {
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
[frames addObject:frame];
repeatCount = 1;
index++;
}
previousImage = image;
// last one
if (idx == frameCount - 1) {
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
[frames addObject:frame];
}
}];
#else
NSBitmapImageRep *bitmapRep;
for (NSImageRep *imageRep in animatedImage.representations) {
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapRep = (NSBitmapImageRep *)imageRep;
break;
}
}
if (bitmapRep) {
frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
}
if (frameCount == 0) {
return nil;
}
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
// NSBitmapImageRep need to manually change frame. "Good taste" API
[bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapRep.CGImage size:CGSizeZero];
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:frameImage duration:frameDuration];
[frames addObject:frame];
}
}
#endif
return frames;
}
#if SD_UIKIT || SD_WATCH
// Convert an EXIF image orientation to an iOS one.
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
// CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
UIImageOrientation imageOrientation = UIImageOrientationUp;
switch (exifOrientation) {
case 1:
imageOrientation = UIImageOrientationUp;
break;
case 3:
imageOrientation = UIImageOrientationDown;
break;
case 8:
imageOrientation = UIImageOrientationLeft;
break;
case 6:
imageOrientation = UIImageOrientationRight;
break;
case 2:
imageOrientation = UIImageOrientationUpMirrored;
break;
case 4:
imageOrientation = UIImageOrientationDownMirrored;
break;
case 5:
imageOrientation = UIImageOrientationLeftMirrored;
break;
case 7:
imageOrientation = UIImageOrientationRightMirrored;
break;
default:
break;
}
return imageOrientation;
}
// Convert an iOS orientation to an EXIF image orientation.
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
// CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
NSInteger exifOrientation = 1;
switch (imageOrientation) {
case UIImageOrientationUp:
exifOrientation = 1;
break;
case UIImageOrientationDown:
exifOrientation = 3;
break;
case UIImageOrientationLeft:
exifOrientation = 8;
break;
case UIImageOrientationRight:
exifOrientation = 6;
break;
case UIImageOrientationUpMirrored:
exifOrientation = 2;
break;
case UIImageOrientationDownMirrored:
exifOrientation = 4;
break;
case UIImageOrientationLeftMirrored:
exifOrientation = 5;
break;
case UIImageOrientationRightMirrored:
exifOrientation = 7;
break;
default:
break;
}
return exifOrientation;
}
#endif
#pragma mark - Helper Fuction
#if SD_UIKIT || SD_WATCH
static NSUInteger gcd(NSUInteger a, NSUInteger b) {
NSUInteger c;
while (a != 0) {
c = a;
a = b % a;
b = c;
}
return b;
}
static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
if (count == 0) {
return 0;
}
NSUInteger result = values[0];
for (size_t i = 1; i < count; ++i) {
result = gcd(values[i], result);
}
return result;
}
#endif
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCoder.h"
/**
Global object holding the array of coders, so that we avoid passing them from object to object.
Uses a priority queue behind scenes, which means the latest added coders have the highest priority.
This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data.
That way, users can add their custom coders while preserving our existing prebuilt ones
Note: the `coders` getter will return the coders in their reversed order
Example:
- by default we internally set coders = `IOCoder`, `WebPCoder`. (`GIFCoder` is not recommended to add only if you want to get GIF support without `FLAnimatedImage`)
- calling `coders` will return `@[WebPCoder, IOCoder]`
- call `[addCoder:[MyCrazyCoder new]]`
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, IOCoder]`
Coders
------
A coder must conform to the `SDWebImageCoder` protocol or even to `SDWebImageProgressiveCoder` if it supports progressive decoding
Conformance is important because that way, they will implement `canDecodeFromData` or `canEncodeToFormat`
Those methods are called on each coder in the array (using the priority order) until one of them returns YES.
That means that coder can decode that data / encode to that format
*/
@interface SDWebImageCodersManager : NSObject<SDWebImageCoder>
/**
Shared reusable instance
*/
+ (nonnull instancetype)sharedInstance;
/**
All coders in coders manager. The coders array is a priority queue, which means the later added coder will have the highest priority
*/
@property (nonatomic, copy, readwrite, nullable) NSArray<id<SDWebImageCoder>> *coders;
/**
Add a new coder to the end of coders array. Which has the highest priority.
@param coder coder
*/
- (void)addCoder:(nonnull id<SDWebImageCoder>)coder;
/**
Remove a coder in the coders array.
@param coder coder
*/
- (void)removeCoder:(nonnull id<SDWebImageCoder>)coder;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCodersManager.h"
#import "SDWebImageImageIOCoder.h"
#import "SDWebImageGIFCoder.h"
#ifdef SD_WEBP
#import "SDWebImageWebPCoder.h"
#endif
#import "UIImage+MultiFormat.h"
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
@interface SDWebImageCodersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t codersLock;
@end
@implementation SDWebImageCodersManager
+ (nonnull instancetype)sharedInstance {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (instancetype)init {
if (self = [super init]) {
// initialize with default coders
NSMutableArray<id<SDWebImageCoder>> *mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder]] mutableCopy];
#ifdef SD_WEBP
[mutableCoders addObject:[SDWebImageWebPCoder sharedCoder]];
#endif
_coders = [mutableCoders copy];
_codersLock = dispatch_semaphore_create(1);
}
return self;
}
#pragma mark - Coder IO operations
- (void)addCoder:(nonnull id<SDWebImageCoder>)coder {
if (![coder conformsToProtocol:@protocol(SDWebImageCoder)]) {
return;
}
LOCK(self.codersLock);
NSMutableArray<id<SDWebImageCoder>> *mutableCoders = [self.coders mutableCopy];
if (!mutableCoders) {
mutableCoders = [NSMutableArray array];
}
[mutableCoders addObject:coder];
self.coders = [mutableCoders copy];
UNLOCK(self.codersLock);
}
- (void)removeCoder:(nonnull id<SDWebImageCoder>)coder {
if (![coder conformsToProtocol:@protocol(SDWebImageCoder)]) {
return;
}
LOCK(self.codersLock);
NSMutableArray<id<SDWebImageCoder>> *mutableCoders = [self.coders mutableCopy];
[mutableCoders removeObject:coder];
self.coders = [mutableCoders copy];
UNLOCK(self.codersLock);
}
#pragma mark - SDWebImageCoder
- (BOOL)canDecodeFromData:(NSData *)data {
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canDecodeFromData:data]) {
return YES;
}
}
return NO;
}
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canEncodeToFormat:format]) {
return YES;
}
}
return NO;
}
- (UIImage *)decodedImageWithData:(NSData *)data {
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canDecodeFromData:data]) {
return [coder decodedImageWithData:data];
}
}
return nil;
}
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
if (!image) {
return nil;
}
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canDecodeFromData:*data]) {
UIImage *decompressedImage = [coder decompressedImageWithImage:image data:data options:optionsDict];
decompressedImage.sd_imageFormat = image.sd_imageFormat;
return decompressedImage;
}
}
return nil;
}
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
if (!image) {
return nil;
}
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canEncodeToFormat:format]) {
return [coder encodedDataWithImage:image format:format];
}
}
return nil;
}
@end
...@@ -10,73 +10,25 @@ ...@@ -10,73 +10,25 @@
#import <TargetConditionals.h> #import <TargetConditionals.h>
#ifdef __OBJC_GC__ #ifdef __OBJC_GC__
#error SDWebImage does not support Objective-C Garbage Collection #error SDWebImage does not support Objective-C Garbage Collection
#endif #endif
// Apple's defines from TargetConditionals.h are a bit weird. #if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
// Seems like TARGET_OS_MAC is always defined (on all platforms). #error SDWebImage doesn't support Deployment Target version < 5.0
// To determine if we are running on OSX, we can only rely on TARGET_OS_IPHONE=0 and all the other platforms
#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
#define SD_MAC 1
#else
#define SD_MAC 0
#endif #endif
// iOS and tvOS are very similar, UIKit exists on both platforms #if !TARGET_OS_IPHONE
// Note: watchOS also has UIKit, but it's very limited #import <AppKit/AppKit.h>
#if TARGET_OS_IOS || TARGET_OS_TV #ifndef UIImage
#define SD_UIKIT 1 #define UIImage NSImage
#else
#define SD_UIKIT 0
#endif
#if TARGET_OS_IOS
#define SD_IOS 1
#else
#define SD_IOS 0
#endif #endif
#ifndef UIImageView
#if TARGET_OS_TV #define UIImageView NSImageView
#define SD_TV 1
#else
#define SD_TV 0
#endif #endif
#if TARGET_OS_WATCH
#define SD_WATCH 1
#else #else
#define SD_WATCH 0
#endif
#import <UIKit/UIKit.h>
#if SD_MAC
#import <AppKit/AppKit.h>
#ifndef UIImage
#define UIImage NSImage
#endif
#ifndef UIImageView
#define UIImageView NSImageView
#endif
#ifndef UIView
#define UIView NSView
#endif
#else
#if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
#error SDWebImage doesn't support Deployment Target version < 5.0
#endif
#if SD_UIKIT
#import <UIKit/UIKit.h>
#endif
#if SD_WATCH
#import <WatchKit/WatchKit.h>
#ifndef UIView
#define UIView WKInterfaceObject
#endif
#ifndef UIImageView
#define UIImageView WKInterfaceImage
#endif
#endif
#endif #endif
#ifndef NS_ENUM #ifndef NS_ENUM
...@@ -87,21 +39,34 @@ ...@@ -87,21 +39,34 @@
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type #define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif #endif
FOUNDATION_EXPORT UIImage *SDScaledImageForKey(NSString *key, UIImage *image); #if OS_OBJECT_USE_OBJC
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q)
#define SDDispatchQueueSetterSementics strong
#else
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q) (dispatch_release(q))
#define SDDispatchQueueSetterSementics assign
#endif
typedef void(^SDWebImageNoParamsBlock)(void); extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
FOUNDATION_EXPORT NSString *const SDWebImageErrorDomain; typedef void(^SDWebImageNoParamsBlock)();
#ifndef dispatch_queue_async_safe extern NSString *const SDWebImageErrorDomain;
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\ #define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\ block();\
} else {\ } else {\
dispatch_async(queue, block);\ dispatch_sync(dispatch_get_main_queue(), block);\
} }
#endif
#ifndef dispatch_main_async_safe #define dispatch_main_async_safe(block)\
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block) if ([NSThread isMainThread]) {\
#endif block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
/* //
* This file is part of the SDWebImage package. // SDWebImageCompat.m
* (c) Olivier Poitrey <rs@dailymotion.com> // SDWebImage
* //
* For the full copyright and license information, please view the LICENSE // Created by Olivier Poitrey on 11/12/12.
* file that was distributed with this source code. // Copyright (c) 2012 Dailymotion. All rights reserved.
*/ //
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "UIImage+MultiFormat.h"
#if !__has_feature(objc_arc) #if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif #endif
#if !OS_OBJECT_USE_OBJC inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) {
#error SDWebImage need ARC for dispatch object
#endif
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) { if (!image) {
return nil; return nil;
} }
#if SD_MAC if ([image.images count] > 0) {
return image; NSMutableArray *scaledImages = [NSMutableArray array];
#elif SD_UIKIT || SD_WATCH
if ((image.images).count > 0) {
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) { for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)]; [scaledImages addObject:SDScaledImageForKey(key, tempImage)];
} }
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration]; return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
if (animatedImage) { }
animatedImage.sd_imageLoopCount = image.sd_imageLoopCount; else {
animatedImage.sd_imageFormat = image.sd_imageFormat;
}
return animatedImage;
} else {
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
#endif
CGFloat scale = 1; CGFloat scale = 1;
if (key.length >= 8) { if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."]; NSRange range = [key rangeOfString:@"@2x."];
...@@ -58,12 +42,10 @@ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullabl ...@@ -58,12 +42,10 @@ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullabl
} }
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
scaledImage.sd_imageFormat = image.sd_imageFormat;
image = scaledImage; image = scaledImage;
} }
return image; return image;
} }
#endif
} }
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain"; NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";
...@@ -2,16 +2,17 @@ ...@@ -2,16 +2,17 @@
* This file is part of the SDWebImage package. * This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com> * (c) Olivier Poitrey <rs@dailymotion.com>
* *
* Created by james <https://github.com/mystcolor> on 9/28/11.
*
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
@interface UIImage (ForceDecode) @interface UIImage (ForceDecode)
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image; + (UIImage *)decodedImageWithImage:(UIImage *)image;
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image;
@end @end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* Created by james <https://github.com/mystcolor> on 9/28/11.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDecoder.h"
@implementation UIImage (ForceDecode)
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
// while downloading huge amount of images
// autorelease the bitmap context
// and all vars to help system to free memory
// when there are memory warning.
// on iOS7, do not forget to call
// [[SDImageCache sharedImageCache] clearMemory];
if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
return nil;
}
@autoreleasepool{
// do not decode animated images
if (image.images != nil) {
return image;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
if (anyAlpha) {
return image;
}
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
bitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
if (unsupportedColorSpace) {
CGColorSpaceRelease(colorspaceRef);
}
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
@end
...@@ -11,14 +11,7 @@ ...@@ -11,14 +11,7 @@
#import "SDWebImageOperation.h" #import "SDWebImageOperation.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
/**
* Put the download in the low queue priority and task priority.
*/
SDWebImageDownloaderLowPriority = 1 << 0, SDWebImageDownloaderLowPriority = 1 << 0,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
*/
SDWebImageDownloaderProgressiveDownload = 1 << 1, SDWebImageDownloaderProgressiveDownload = 1 << 1,
/** /**
...@@ -31,12 +24,13 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { ...@@ -31,12 +24,13 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
* Call completion block with nil image/imageData if the image was read from NSURLCache * Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`). * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/ */
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/** /**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled. * extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/ */
SDWebImageDownloaderContinueInBackground = 1 << 4, SDWebImageDownloaderContinueInBackground = 1 << 4,
/** /**
...@@ -52,14 +46,9 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { ...@@ -52,14 +46,9 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/** /**
* Put the download in the high queue priority and task priority. * Put the image in the high priority queue.
*/ */
SDWebImageDownloaderHighPriority = 1 << 7, SDWebImageDownloaderHighPriority = 1 << 7,
/**
* Scale down the image
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
}; };
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
...@@ -74,35 +63,14 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { ...@@ -74,35 +63,14 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
SDWebImageDownloaderLIFOExecutionOrder SDWebImageDownloaderLIFOExecutionOrder
}; };
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification; extern NSString *const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification; extern NSString *const SDWebImageDownloadStopNotification;
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary; typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);
/**
* A token associated with each download. Can be used to cancel a download
*/
@interface SDWebImageDownloadToken : NSObject <SDWebImageOperation>
/**
The download's URL. This should be readonly and you should not modify
*/
@property (nonatomic, strong, nullable) NSURL *url;
/**
The cancel token taken from `addHandlersForProgress:completed`. This should be readonly and you should not modify
@note use `-[SDWebImageDownloadToken cancel]` to cancel the token
*/
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
/** /**
* Asynchronous downloader dedicated and optimized for image loading. * Asynchronous downloader dedicated and optimized for image loading.
...@@ -115,9 +83,6 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -115,9 +83,6 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
*/ */
@property (assign, nonatomic) BOOL shouldDecompressImages; @property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* The maximum number of concurrent downloads
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads; @property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/** /**
...@@ -125,19 +90,12 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -125,19 +90,12 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
*/ */
@property (readonly, nonatomic) NSUInteger currentDownloadCount; @property (readonly, nonatomic) NSUInteger currentDownloadCount;
/** /**
* The timeout value (in seconds) for the download operation. Default: 15.0. * The timeout value (in seconds) for the download operation. Default: 15.0.
*/ */
@property (assign, nonatomic) NSTimeInterval downloadTimeout; @property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* The configuration in use by the internal NSURLSession.
* Mutating this object directly has no effect.
*
* @see createNewSessionWithConfiguration:
*/
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
/** /**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`. * Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
...@@ -149,22 +107,22 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -149,22 +107,22 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* *
* @return global shared instance of downloader class * @return global shared instance of downloader class
*/ */
+ (nonnull instancetype)sharedDownloader; + (SDWebImageDownloader *)sharedDownloader;
/** /**
* Set the default URL credential to be set for request operations. * Set the default URL credential to be set for request operations.
*/ */
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential; @property (strong, nonatomic) NSURLCredential *urlCredential;
/** /**
* Set username * Set username
*/ */
@property (strong, nonatomic, nullable) NSString *username; @property (strong, nonatomic) NSString *username;
/** /**
* Set password * Set password
*/ */
@property (strong, nonatomic, nullable) NSString *password; @property (strong, nonatomic) NSString *password;
/** /**
* Set filter to pick headers for downloading image HTTP request. * Set filter to pick headers for downloading image HTTP request.
...@@ -172,14 +130,7 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -172,14 +130,7 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* This block will be invoked for each downloading image request, returned * This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request. * NSDictionary will be used as headers in corresponding HTTP request.
*/ */
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter; @property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
* Creates an instance of a downloader with specified session configuration.
* @note `timeoutIntervalForRequest` is going to be overwritten.
* @return new instance of downloader class
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/** /**
* Set a value for a HTTP header to be appended to each download HTTP request. * Set a value for a HTTP header to be appended to each download HTTP request.
...@@ -187,14 +138,14 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -187,14 +138,14 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* @param value The value for the header field. Use `nil` value to remove the header. * @param value The value for the header field. Use `nil` value to remove the header.
* @param field The name of the header field to set. * @param field The name of the header field to set.
*/ */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field; - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
/** /**
* Returns the value of the specified HTTP header field. * Returns the value of the specified HTTP header field.
* *
* @return The value associated with the header field field, or `nil` if there is no corresponding header field. * @return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/ */
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field; - (NSString *)valueForHTTPHeaderField:(NSString *)field;
/** /**
* Sets a subclass of `SDWebImageDownloaderOperation` as the default * Sets a subclass of `SDWebImageDownloaderOperation` as the default
...@@ -204,7 +155,7 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -204,7 +155,7 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* @param operationClass The subclass of `SDWebImageDownloaderOperation` to set * @param operationClass The subclass of `SDWebImageDownloaderOperation` to set
* as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`. * as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
*/ */
- (void)setOperationClass:(nullable Class)operationClass; - (void)setOperationClass:(Class)operationClass;
/** /**
* Creates a SDWebImageDownloader async downloader instance with a given URL * Creates a SDWebImageDownloader async downloader instance with a given URL
...@@ -216,7 +167,6 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -216,7 +167,6 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* @param url The URL to the image to download * @param url The URL to the image to download
* @param options The options to be used for this download * @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading * @param progressBlock A block called repeatedly while the image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called once the download is completed. * @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error, * If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES * error parameter is set with the error. The last parameter is always YES
...@@ -226,19 +176,12 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -226,19 +176,12 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
* before to be called a last time with the full image and finished argument * before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES. * set to YES. In case of error, the finished argument is always YES.
* *
* @return A token (SDWebImageDownloadToken) that can be passed to -cancel: to cancel this operation * @return A cancellable SDWebImageOperation
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
*
* @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
*/ */
- (void)cancel:(nullable SDWebImageDownloadToken *)token; - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
/** /**
* Sets the download queue suspension state * Sets the download queue suspension state
...@@ -250,22 +193,4 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB ...@@ -250,22 +193,4 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
*/ */
- (void)cancelAllDownloads; - (void)cancelAllDownloads;
/**
* Forces SDWebImageDownloader to create and use a new NSURLSession that is
* initialized with the given configuration.
* @note All existing download operations in the queue will be cancelled.
* @note `timeoutIntervalForRequest` is going to be overwritten.
*
* @param sessionConfiguration The configuration to use for the new NSURLSession
*/
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
/**
* Invalidates the managed session, optionally canceling pending operations.
* @note If you use custom downloader instead of the shared downloader, you need call this method when you do not use it to avoid memory leak
* @param cancelPendingOperations Whether or not to cancel pending operations.
* @note Calling this method on the shared downloader has no effect.
*/
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
@end @end
...@@ -8,39 +8,20 @@ ...@@ -8,39 +8,20 @@
#import "SDWebImageDownloader.h" #import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h" #import "SDWebImageDownloaderOperation.h"
#import <ImageIO/ImageIO.h>
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); static NSString *const kProgressCallbackKey = @"progress";
#define UNLOCK(lock) dispatch_semaphore_signal(lock); static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloadToken ()
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;
@end
@implementation SDWebImageDownloadToken
- (void)cancel {
if (self.downloadOperation) {
SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
if (cancelToken) {
[self.downloadOperation cancel:cancelToken];
}
}
}
@end
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate> @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue; @property (strong, nonatomic) NSOperationQueue *downloadQueue;
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation; @property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (assign, nonatomic, nullable) Class operationClass; @property (assign, nonatomic) Class operationClass;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations; @property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders; @property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// The session in which data tasks will run // The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session; @property (strong, nonatomic) NSURLSession *session;
...@@ -72,7 +53,7 @@ ...@@ -72,7 +53,7 @@
} }
} }
+ (nonnull instancetype)sharedDownloader { + (SDWebImageDownloader *)sharedDownloader {
static dispatch_once_t once; static dispatch_once_t once;
static id instance; static id instance;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
...@@ -81,11 +62,7 @@ ...@@ -81,11 +62,7 @@
return instance; return instance;
} }
- (nonnull instancetype)init { - (id)init {
return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
}
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) { if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class]; _operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES; _shouldDecompressImages = YES;
...@@ -93,80 +70,49 @@ ...@@ -93,80 +70,49 @@
_downloadQueue = [NSOperationQueue new]; _downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6; _downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader"; _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new]; _URLCallbacks = [NSMutableDictionary new];
#ifdef SD_WEBP #ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy]; _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else #else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy]; _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif #endif
_operationsLock = dispatch_semaphore_create(1); _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_headersLock = dispatch_semaphore_create(1);
_downloadTimeout = 15.0; _downloadTimeout = 15.0;
[self createNewSessionWithConfiguration:sessionConfiguration]; NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
} }
return self; return self;
} }
- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
[self cancelAllDownloads];
if (self.session) {
[self.session invalidateAndCancel];
}
sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
if (self == [SDWebImageDownloader sharedDownloader]) {
return;
}
if (cancelPendingOperations) {
[self.session invalidateAndCancel];
} else {
[self.session finishTasksAndInvalidate];
}
}
- (void)dealloc { - (void)dealloc {
[self.session invalidateAndCancel]; [self.session invalidateAndCancel];
self.session = nil; self.session = nil;
[self.downloadQueue cancelAllOperations]; [self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
} }
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field { - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
LOCK(self.headersLock);
if (value) { if (value) {
self.HTTPHeaders[field] = value; self.HTTPHeaders[field] = value;
} else {
[self.HTTPHeaders removeObjectForKey:field];
} }
UNLOCK(self.headersLock); else {
} [self.HTTPHeaders removeObjectForKey:field];
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) {
return nil;
} }
return [[self allHTTPHeaderFields] objectForKey:field];
} }
- (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields { - (NSString *)valueForHTTPHeaderField:(NSString *)field {
LOCK(self.headersLock); return self.HTTPHeaders[field];
SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
UNLOCK(self.headersLock);
return allHTTPHeaderFields;
} }
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads { - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
...@@ -181,52 +127,75 @@ ...@@ -181,52 +127,75 @@
return _downloadQueue.maxConcurrentOperationCount; return _downloadQueue.maxConcurrentOperationCount;
} }
- (NSURLSessionConfiguration *)sessionConfiguration { - (void)setOperationClass:(Class)operationClass {
return self.session.configuration; _operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
} }
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
options:(SDWebImageDownloaderOptions)options __block SDWebImageDownloaderOperation *operation;
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock __weak __typeof(self)wself = self;
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{ [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
__strong __typeof (wself) sself = wself; NSTimeInterval timeoutInterval = wself.downloadTimeout;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) { if (timeoutInterval == 0.0) {
timeoutInterval = 15.0; timeoutInterval = 15.0;
} }
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES; request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) { if (wself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]); request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
} }
else { else {
request.allHTTPHeaderFields = [sself allHTTPHeaderFields]; request.allHTTPHeaderFields = wself.HTTPHeaders;
} }
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options]; operation = [[wself.operationClass alloc] initWithRequest:request
operation.shouldDecompressImages = sself.shouldDecompressImages; inSession:self.session
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages;
if (sself.urlCredential) { if (wself.urlCredential) {
operation.credential = sself.urlCredential; operation.credential = wself.urlCredential;
} else if (sself.username && sself.password) { } else if (wself.username && wself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession]; operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
} }
if (options & SDWebImageDownloaderHighPriority) { if (options & SDWebImageDownloaderHighPriority) {
...@@ -234,79 +203,50 @@ ...@@ -234,79 +203,50 @@
} else if (options & SDWebImageDownloaderLowPriority) { } else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow; operation.queuePriority = NSOperationQueuePriorityLow;
} }
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { [wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation]; [wself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation; wself.lastAddedOperation = operation;
} }
return operation;
}]; }];
}
- (void)cancel:(nullable SDWebImageDownloadToken *)token { return operation;
NSURL *url = token.url;
if (!url) {
return;
}
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
UNLOCK(self.operationsLock);
} }
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock - (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data. // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) { if (url == nil) {
if (completedBlock != nil) { if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO); completedBlock(nil, nil, nil, NO);
} }
return nil; return;
}
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished) {
operation = createCallback();
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
[self.downloadQueue addOperation:operation];
} }
UNLOCK(self.operationsLock);
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new]; if (!self.URLCallbacks[url]) {
token.downloadOperation = operation; self.URLCallbacks[url] = [NSMutableArray new];
token.url = url; first = YES;
token.downloadOperationCancelToken = downloadOperationCancelToken; }
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
return token; if (first) {
createCallback();
}
});
} }
- (void)setSuspended:(BOOL)suspended { - (void)setSuspended:(BOOL)suspended {
self.downloadQueue.suspended = suspended; [self.downloadQueue setSuspended:suspended];
} }
- (void)cancelAllDownloads { - (void)cancelAllDownloads {
...@@ -335,22 +275,16 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -335,22 +275,16 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask]; SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
} }
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask]; SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data]; [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
} }
- (void)URLSession:(NSURLSession *)session - (void)URLSession:(NSURLSession *)session
...@@ -360,50 +294,25 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -360,50 +294,25 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask]; SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
} }
#pragma mark NSURLSessionTaskDelegate #pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task]; SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { [dataOperation URLSession:session task:task didCompleteWithError:error];
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(request);
}
}
} }
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task]; SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
} }
@end @end
...@@ -10,48 +10,22 @@ ...@@ -10,48 +10,22 @@
#import "SDWebImageDownloader.h" #import "SDWebImageDownloader.h"
#import "SDWebImageOperation.h" #import "SDWebImageOperation.h"
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification; extern NSString *const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification; extern NSString *const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification; extern NSString *const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification; extern NSString *const SDWebImageDownloadFinishNotification;
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
/**
Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol
For the description about these methods, see `SDWebImageDownloaderOperation`
*/
@protocol SDWebImageDownloaderOperationInterface<NSObject>
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value;
- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value;
- (BOOL)cancel:(nullable id)token;
@end
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
/** /**
* The request used by the operation's task. * The request used by the operation's task.
*/ */
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request; @property (strong, nonatomic, readonly) NSURLRequest *request;
/** /**
* The operation's task * The operation's task
*/ */
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; @property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
@property (assign, nonatomic) BOOL shouldDecompressImages; @property (assign, nonatomic) BOOL shouldDecompressImages;
...@@ -63,11 +37,11 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification ...@@ -63,11 +37,11 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility"); @property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
/** /**
* The credential used for authentication challenges in `-URLSession:task:didReceiveChallenge:completionHandler:`. * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
* *
* This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. * This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
*/ */
@property (nonatomic, strong, nullable) NSURLCredential *credential; @property (nonatomic, strong) NSURLCredential *credential;
/** /**
* The SDWebImageDownloaderOptions for the receiver. * The SDWebImageDownloaderOptions for the receiver.
...@@ -80,9 +54,9 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification ...@@ -80,9 +54,9 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
@property (assign, nonatomic) NSInteger expectedSize; @property (assign, nonatomic) NSInteger expectedSize;
/** /**
* The response returned by the operation's task. * The response returned by the operation's connection.
*/ */
@property (strong, nonatomic, nullable) NSURLResponse *response; @property (strong, nonatomic) NSURLResponse *response;
/** /**
* Initializes a `SDWebImageDownloaderOperation` object * Initializes a `SDWebImageDownloaderOperation` object
...@@ -92,34 +66,41 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification ...@@ -92,34 +66,41 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
* @param request the URL request * @param request the URL request
* @param session the URL session in which this operation will run * @param session the URL session in which this operation will run
* @param options downloader options * @param options downloader options
* @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
* @param cancelBlock the block executed if the download (operation) is cancelled
* *
* @return the initialized instance * @return the initialized instance
*/ */
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request - (id)initWithRequest:(NSURLRequest *)request
inSession:(nullable NSURLSession *)session inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER; options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
/** /**
* Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of * Initializes a `SDWebImageDownloaderOperation` object
* callbacks.
* *
* @see SDWebImageDownloaderOperation
*
* @param request the URL request
* @param options downloader options
* @param progressBlock the block executed when a new chunk of data arrives. * @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue * @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done. * @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
* @param cancelBlock the block executed if the download (operation) is cancelled
* *
* @return the token to use to cancel this set of handlers * @return the initialized instance. The operation will run in a separate session created for this operation
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled.
*
* @param token the token representing a set of callbacks to cancel
*
* @return YES if the operation was stopped because this was the last token to be canceled. NO otherwise.
*/ */
- (BOOL)cancel:(nullable id)token; - (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock
__deprecated_msg("Method deprecated. Use `initWithRequest:inSession:options:progress:completed:cancelled`");
@end @end
...@@ -7,119 +7,87 @@ ...@@ -7,119 +7,87 @@
*/ */
#import "SDWebImageDownloaderOperation.h" #import "SDWebImageDownloaderOperation.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <ImageIO/ImageIO.h>
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
#import "NSImage+WebCache.h"
#import "SDWebImageCodersManager.h"
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
// iOS 8 Foundation.framework extern these symbol but the define is in CFNetwork.framework. We just fix this without import CFNetwork.framework
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const float NSURLSessionTaskPriorityHigh = 0.75;
const float NSURLSessionTaskPriorityDefault = 0.5;
const float NSURLSessionTaskPriorityLow = 0.25;
#endif
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification"; NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation () @interface SDWebImageDownloaderOperation ()
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks; @property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished; @property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic, nullable) NSMutableData *imageData; @property (strong, nonatomic) NSMutableData *imageData;
@property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse`
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation // the task associated with this operation
@property (weak, nonatomic, nullable) NSURLSession *unownedSession; @property (weak, nonatomic) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one // This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
@property (strong, nonatomic, nullable) NSURLSession *ownedSession; @property (strong, nonatomic) NSURLSession *ownedSession;
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask; @property (strong, nonatomic, readwrite) NSURLSessionTask *dataTask;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock; // a lock to keep the access to `callbackBlocks` thread-safe @property (strong, atomic) NSThread *thread;
@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; // the queue to do image decoding #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
#if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif #endif
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
@end @end
@implementation SDWebImageDownloaderOperation @implementation SDWebImageDownloaderOperation {
size_t width, height;
UIImageOrientation orientation;
BOOL responseFromCached;
}
@synthesize executing = _executing; @synthesize executing = _executing;
@synthesize finished = _finished; @synthesize finished = _finished;
- (nonnull instancetype)init { - (id)initWithRequest:(NSURLRequest *)request
return [self initWithRequest:nil inSession:nil options:0]; options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
return [self initWithRequest:request
inSession:nil
options:options
progress:progressBlock
completed:completedBlock
cancelled:cancelBlock];
} }
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request - (id)initWithRequest:(NSURLRequest *)request
inSession:(nullable NSURLSession *)session inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options { options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) { if ((self = [super init])) {
_request = [request copy]; _request = [request copy];
_shouldDecompressImages = YES; _shouldDecompressImages = YES;
_options = options; _options = options;
_callbackBlocks = [NSMutableArray new]; _progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO; _executing = NO;
_finished = NO; _finished = NO;
_expectedSize = 0; _expectedSize = 0;
_unownedSession = session; _unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1); responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
} }
return self; return self;
} }
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
UNLOCK(self.callbacksLock);
return callbacks;
}
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
LOCK(self.callbacksLock);
NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
UNLOCK(self.callbacksLock);
// We need to remove [NSNull null] because there might not always be a progress block for each callback
[callbacks removeObjectIdenticalTo:[NSNull null]];
return [callbacks copy]; // strip mutability here
}
- (BOOL)cancel:(nullable id)token {
BOOL shouldCancel = NO;
LOCK(self.callbacksLock);
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
UNLOCK(self.callbacksLock);
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
- (void)start { - (void)start {
@synchronized (self) { @synchronized (self) {
if (self.isCancelled) { if (self.isCancelled) {
...@@ -128,7 +96,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -128,7 +96,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
return; return;
} }
#if SD_UIKIT #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication"); Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)]; BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) { if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
...@@ -147,7 +115,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -147,7 +115,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
} }
#endif #endif
NSURLSession *session = self.unownedSession; NSURLSession *session = self.unownedSession;
if (!session) { if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15; sessionConfig.timeoutIntervalForRequest = 15;
...@@ -156,58 +124,34 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -156,58 +124,34 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls. * method calls and completion handler calls.
*/ */
session = [NSURLSession sessionWithConfiguration:sessionConfig self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self delegate:self
delegateQueue:nil]; delegateQueue:nil];
self.ownedSession = session; session = self.ownedSession;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
} }
self.dataTask = [session dataTaskWithRequest:self.request]; self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES; self.executing = YES;
self.thread = [NSThread currentThread];
} }
[self.dataTask resume];
if (self.dataTask) { if (self.dataTask) {
#pragma clang diagnostic push if (self.progressBlock) {
#pragma clang diagnostic ignored "-Wunguarded-availability" self.progressBlock(0, NSURLResponseUnknownLength);
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
} }
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
}); });
} else { }
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]]; else {
[self done]; if (self.completedBlock) {
return; self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
} }
#if SD_UIKIT #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication"); Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return; return;
...@@ -222,22 +166,32 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -222,22 +166,32 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
- (void)cancel { - (void)cancel {
@synchronized (self) { @synchronized (self) {
[self cancelInternal]; if (self.thread) {
[self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
else {
[self cancelInternal];
}
} }
} }
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
}
- (void)cancelInternal { - (void)cancelInternal {
if (self.isFinished) return; if (self.isFinished) return;
[super cancel]; [super cancel];
if (self.cancelBlock) self.cancelBlock();
if (self.dataTask) { if (self.dataTask) {
[self.dataTask cancel]; [self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
}); });
// As we cancelled the task, its callback won't be called and thus won't // As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags. // maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO; if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES; if (!self.isFinished) self.finished = YES;
...@@ -253,11 +207,12 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -253,11 +207,12 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
} }
- (void)reset { - (void)reset {
LOCK(self.callbacksLock); self.cancelBlock = nil;
[self.callbackBlocks removeAllObjects]; self.completedBlock = nil;
UNLOCK(self.callbacksLock); self.progressBlock = nil;
self.dataTask = nil; self.dataTask = nil;
self.imageData = nil;
self.thread = nil;
if (self.ownedSession) { if (self.ownedSession) {
[self.ownedSession invalidateAndCancel]; [self.ownedSession invalidateAndCancel];
self.ownedSession = nil; self.ownedSession = nil;
...@@ -286,82 +241,128 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; ...@@ -286,82 +241,128 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
dataTask:(NSURLSessionDataTask *)dataTask dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
self.expectedSize = expected;
self.response = response;
NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
BOOL valid = statusCode < 400;
//'304 Not Modified' is an exceptional one. It should be treated as cancelled if no cache data
//URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check
if (statusCode == 304 && !self.cachedData) {
valid = NO;
}
if (valid) { //'304 Not Modified' is an exceptional one
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
progressBlock(0, expected, self.request.URL); NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
if (self.progressBlock) {
self.progressBlock(0, expected);
} }
} else {
// Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
disposition = NSURLSessionResponseCancel; self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
[self done];
} }
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
if (completionHandler) { if (completionHandler) {
completionHandler(disposition); completionHandler(NSURLSessionResponseAllow);
} }
} }
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
if (!self.imageData) {
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
}
[self.imageData appendData:data]; [self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) { if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// Get the image data // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
__block NSData *imageData = [self.imageData copy]; // Thanks to the author @Nyx0uf
// Get the total bytes downloaded // Get the total bytes downloaded
const NSInteger totalSize = imageData.length; const NSInteger totalSize = self.imageData.length;
// Get the finish status
BOOL finished = (totalSize >= self.expectedSize); // Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
if (!self.progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts if (width + height == 0) {
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] && if (properties) {
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) { NSInteger orientationValue = -1;
self.progressiveCoder = [[[coder class] alloc] init]; CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
break; if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
} val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
} }
} }
// progressive decode the image in coder queue if (width + height > 0 && totalSize < self.expectedSize) {
dispatch_async(self.coderQueue, ^{ // Create the image
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished]; CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (image) {
#ifdef TARGET_OS_IPHONE
// Workaround for iOS anamorphic image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif
if (partialImageRef) {
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image]; UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) { if (self.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}]; image = [UIImage decodedImageWithImage:scaledImage];
} }
else {
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding. image = scaledImage;
}
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO]; CGImageRelease(partialImageRef);
dispatch_main_sync_safe(^{
if (self.completedBlock) {
self.completedBlock(image, nil, nil, NO);
}
});
} }
}); }
CFRelease(imageSource);
} }
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { if (self.progressBlock) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL); self.progressBlock(self.imageData.length, self.expectedSize);
} }
} }
...@@ -369,10 +370,11 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -369,10 +370,11 @@ didReceiveResponse:(NSURLResponse *)response
dataTask:(NSURLSessionDataTask *)dataTask dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
NSCachedURLResponse *cachedResponse = proposedResponse; NSCachedURLResponse *cachedResponse = proposedResponse;
if (!(self.options & SDWebImageDownloaderUseNSURLCache)) { if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses // Prevents caching of responses
cachedResponse = nil; cachedResponse = nil;
} }
...@@ -385,77 +387,57 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -385,77 +387,57 @@ didReceiveResponse:(NSURLResponse *)response
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) { @synchronized(self) {
self.thread = nil;
self.dataTask = nil; self.dataTask = nil;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) { if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
} }
}); });
} }
// make sure to call `[self done]` to mark operation as finished
if (error) { if (error) {
[self callCompletionBlocksWithError:error]; if (self.completedBlock) {
[self done]; self.completedBlock(nil, nil, error, YES);
}
} else { } else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) { SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
if (completionBlock) {
/** /**
* If you specified to use `NSURLCache`, then the response you get here is what you need. * See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
* Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
* and images for which responseFromCached is YES (only the ones that cannot be cached).
* Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
*/ */
__block NSData *imageData = [self.imageData copy]; if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
if (imageData) { completionBlock(nil, nil, nil, YES);
/** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`, } else if (self.imageData) {
* then we should check if the cached data is equal to image data UIImage *image = [UIImage sd_imageWithData:self.imageData];
*/ NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) { image = [self scaledImageForKey:key image:image];
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES]; // Do not force decoding animated GIFs
[self done]; if (!image.images) {
} else { if (self.shouldDecompressImages) {
// decode the image in coder queue image = [UIImage decodedImageWithImage:image];
dispatch_async(self.coderQueue, ^{ }
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData]; }
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; if (CGSizeEqualToSize(image.size, CGSizeZero)) {
image = [self scaledImageForKey:key image:image]; completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
BOOL shouldDecode = YES; else {
// Do not force decoding animated GIFs and WebPs completionBlock(image, self.imageData, nil, YES);
if (image.images) {
shouldDecode = NO;
} else {
#ifdef SD_WEBP
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatWebP) {
shouldDecode = NO;
}
#endif
}
if (shouldDecode) {
if (self.shouldDecompressImages) {
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
[self done];
});
} }
} else { } else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]]; completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
[self done];
} }
} else {
[self done];
} }
} }
self.completionBlock = nil;
[self done];
} }
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
...@@ -471,7 +453,7 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -471,7 +453,7 @@ didReceiveResponse:(NSURLResponse *)response
disposition = NSURLSessionAuthChallengeUseCredential; disposition = NSURLSessionAuthChallengeUseCredential;
} }
} else { } else {
if (challenge.previousFailureCount == 0) { if ([challenge previousFailureCount] == 0) {
if (self.credential) { if (self.credential) {
credential = self.credential; credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential; disposition = NSURLSessionAuthChallengeUseCredential;
...@@ -489,28 +471,36 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -489,28 +471,36 @@ didReceiveResponse:(NSURLResponse *)response
} }
#pragma mark Helper methods #pragma mark Helper methods
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (BOOL)shouldContinueWhenAppEntersBackground { + (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
return self.options & SDWebImageDownloaderContinueInBackground; switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
} }
- (void)callCompletionBlocksWithError:(nullable NSError *)error { - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES]; return SDScaledImageForKey(key, image);
} }
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image - (BOOL)shouldContinueWhenAppEntersBackground {
imageData:(nullable NSData *)imageData return self.options & SDWebImageDownloaderContinueInBackground;
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
} }
@end @end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface SDWebImageFrame : NSObject
// This class is used for creating animated images via `animatedImageWithFrames` in `SDWebImageCoderHelper`. Attention if you need to specify animated images loop count, use `sd_imageLoopCount` property in `UIImage+MultiFormat`.
/**
The image of current frame. You should not set an animated image.
*/
@property (nonatomic, strong, readonly, nonnull) UIImage *image;
/**
The duration of current frame to be displayed. The number is seconds but not milliseconds. You should not set this to zero.
*/
@property (nonatomic, readonly, assign) NSTimeInterval duration;
/**
Create a frame instance with specify image and duration
@param image current frame's image
@param duration current frame's duration
@return frame instance
*/
+ (instancetype _Nonnull)frameWithImage:(UIImage * _Nonnull)image duration:(NSTimeInterval)duration;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageFrame.h"
@interface SDWebImageFrame ()
@property (nonatomic, strong, readwrite, nonnull) UIImage *image;
@property (nonatomic, readwrite, assign) NSTimeInterval duration;
@end
@implementation SDWebImageFrame
+ (instancetype)frameWithImage:(UIImage *)image duration:(NSTimeInterval)duration {
SDWebImageFrame *frame = [[SDWebImageFrame alloc] init];
frame.image = image;
frame.duration = duration;
return frame;
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCoder.h"
/**
Built in coder using ImageIO that supports GIF encoding/decoding
@note `SDWebImageIOCoder` supports GIF but only as static (will use the 1st frame).
@note Use `SDWebImageGIFCoder` for fully animated GIFs - less performant than `FLAnimatedImage`
@note If you decide to make all `UIImageView`(including `FLAnimatedImageView`) instance support GIF. You should add this coder to `SDWebImageCodersManager` and make sure that it has a higher priority than `SDWebImageIOCoder`
@note The recommended approach for animated GIFs is using `FLAnimatedImage`. It's more performant than `UIImageView` for GIF displaying
*/
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder>
+ (nonnull instancetype)sharedCoder;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageGIFCoder.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
#import "SDWebImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
@implementation SDWebImageGIFCoder
+ (instancetype)sharedCoder {
static SDWebImageGIFCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageGIFCoder alloc] init];
});
return coder;
}
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
}
- (UIImage *)decodedImageWithData:(NSData *)data {
if (!data) {
return nil;
}
#if SD_MAC
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSImage *animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
[animatedImage addRepresentation:imageRep];
return animatedImage;
#else
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
} else {
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
continue;
}
float duration = [self sd_frameDurationAtIndex:i source:source];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = 1;
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge NSString *)kCGImagePropertyGIFDictionary];
if (gifProperties) {
NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge NSString *)kCGImagePropertyGIFLoopCount];
if (gifLoopCount != nil) {
loopCount = gifLoopCount.unsignedIntegerValue;
}
}
animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
animatedImage.sd_imageFormat = SDImageFormatGIF;
}
CFRelease(source);
return animatedImage;
#endif
}
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
if (!cfFrameProperties) {
return frameDuration;
}
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp != nil) {
frameDuration = [delayTimeUnclampedProp floatValue];
} else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp != nil) {
frameDuration = [delayTimeProp floatValue];
}
}
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
// for more information.
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
}
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
// GIF do not decompress
return image;
}
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return (format == SDImageFormatGIF);
}
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
if (!image) {
return nil;
}
if (format != SDImageFormatGIF) {
return nil;
}
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
// Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
if (frames.count == 0) {
// for static single GIF images
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
} else {
// for animated GIF images
NSUInteger loopCount = image.sd_imageLoopCount;
NSDictionary *gifProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary: @{(__bridge NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
for (size_t i = 0; i < frames.count; i++) {
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
CFRelease(imageDestination);
return [imageData copy];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCoder.h"
/**
Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding.
GIF
Also supports static GIF (meaning will only handle the 1st frame).
For a full GIF support, we recommend `FLAnimatedImage` or our less performant `SDWebImageGIFCoder`
HEIC
This coder also supports HEIC format because ImageIO supports it natively. But it depends on the system capabilities, so it won't work on all devices, see: https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf
Decode(Software): !Simulator && (iOS 11 || tvOS 11 || macOS 10.13)
Decode(Hardware): !Simulator && ((iOS 11 && A9Chip) || (macOS 10.13 && 6thGenerationIntelCPU))
Encode(Software): macOS 10.13
Encode(Hardware): !Simulator && ((iOS 11 && A10FusionChip) || (macOS 10.13 && 6thGenerationIntelCPU))
*/
@interface SDWebImageImageIOCoder : NSObject <SDWebImageProgressiveCoder>
+ (nonnull instancetype)sharedCoder;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageImageIOCoder.h"
#import "SDWebImageCoderHelper.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
#if SD_UIKIT || SD_WATCH
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.0f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
#endif
@implementation SDWebImageImageIOCoder {
size_t _width, _height;
#if SD_UIKIT || SD_WATCH
UIImageOrientation _orientation;
#endif
CGImageSourceRef _imageSource;
}
- (void)dealloc {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
+ (instancetype)sharedCoder {
static SDWebImageImageIOCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageImageIOCoder alloc] init];
});
return coder;
}
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// Do not support WebP decoding
return NO;
case SDImageFormatHEIC:
// Check HEIC decoding compatibility
return [[self class] canDecodeFromHEICFormat];
default:
return YES;
}
}
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// Do not support WebP progressive decoding
return NO;
case SDImageFormatHEIC:
// Check HEIC decoding compatibility
return [[self class] canDecodeFromHEICFormat];
default:
return YES;
}
}
- (UIImage *)decodedImageWithData:(NSData *)data {
if (!data) {
return nil;
}
UIImage *image = [[UIImage alloc] initWithData:data];
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
return image;
}
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
if (!_imageSource) {
_imageSource = CGImageSourceCreateIncremental(NULL);
}
UIImage *image;
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
if (_width + _height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = 1;
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
_orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue];
#endif
}
}
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:1 orientation:_orientation];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
CGImageRelease(partialImageRef);
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
}
}
if (finished) {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
return image;
}
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
#if SD_MAC
return image;
#endif
#if SD_UIKIT || SD_WATCH
BOOL shouldScaleDown = NO;
if (optionsDict != nil) {
NSNumber *scaleDownLargeImagesOption = nil;
if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
}
if (scaleDownLargeImagesOption != nil) {
shouldScaleDown = [scaleDownLargeImagesOption boolValue];
}
}
if (!shouldScaleDown) {
return [self sd_decompressedImageWithImage:image];
} else {
UIImage *scaledDownImage = [self sd_decompressedAndScaledDownImageWithImage:image];
if (scaledDownImage && !CGSizeEqualToSize(scaledDownImage.size, image.size)) {
// if the image is scaled down, need to modify the data pointer as well
SDImageFormat format = [NSData sd_imageFormatForImageData:*data];
NSData *imageData = [self encodedDataWithImage:scaledDownImage format:format];
if (imageData) {
*data = imageData;
}
}
return scaledDownImage;
}
#endif
}
#if SD_UIKIT || SD_WATCH
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
if (![[self class] shouldDecodeImage:image]) {
return image;
}
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool{
CGImageRef imageRef = image.CGImage;
// device color space
CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
// iOS display alpha info (BRGA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (context == NULL) {
return image;
}
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [[UIImage alloc] initWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
if (![[self class] shouldDecodeImage:image]) {
return image;
}
if (![[self class] shouldScaleDownImage:image]) {
return [self sd_decompressedImageWithImage:image];
}
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool {
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
// Determine the scale ratio to apply to the input image
// that results in an output image of the defined size.
// see kDestImageSizeMB, and how it relates to destTotalPixels.
float imageScale = kDestTotalPixels / sourceTotalPixels;
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
// device color space
CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(sourceImageRef);
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (destContext == NULL) {
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// Now define the size of the rectangle to be used for the
// incremental blits from the input image to the output image.
// we use a source tile width equal to the width of the source
// image due to the way that iOS retrieves image data from disk.
// iOS must decode an image from disk in full width 'bands', even
// if current graphics context is clipped to a subrect within that
// band. Therefore we fully utilize all of the pixel data that results
// from a decoding opertion by achnoring our tile size to the full
// width of the input image.
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
// The source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
}
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
#endif
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
case SDImageFormatWebP:
// Do not support WebP encoding
return NO;
case SDImageFormatHEIC:
// Check HEIC encoding compatibility
return [[self class] canEncodeToHEICFormat];
default:
return YES;
}
}
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
if (!image) {
return nil;
}
if (format == SDImageFormatUndefined) {
BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
NSInteger exifOrientation = [SDWebImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
[properties setValue:@(exifOrientation) forKey:(__bridge NSString *)kCGImagePropertyOrientation];
#endif
// Add your image to the destination.
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
CFRelease(imageDestination);
return [imageData copy];
}
#pragma mark - Helper
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
return YES;
}
+ (BOOL)canDecodeFromHEICFormat {
static BOOL canDecode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
#if TARGET_OS_SIMULATOR || SD_WATCH
canDecode = NO;
#elif SD_MAC
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
// macOS 10.13+
canDecode = processInfo.operatingSystemVersion.minorVersion >= 13;
} else {
canDecode = NO;
}
#elif SD_UIKIT
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
// iOS 11+ && tvOS 11+
canDecode = processInfo.operatingSystemVersion.majorVersion >= 11;
} else {
canDecode = NO;
}
#endif
#pragma clang diagnostic pop
});
return canDecode;
}
+ (BOOL)canEncodeToHEICFormat {
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can't encode to HEIC
canEncode = NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
canEncode = YES;
}
});
return canEncode;
}
#if SD_UIKIT || SD_WATCH
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
BOOL shouldScaleDown = YES;
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
float imageScale = kDestTotalPixels / sourceTotalPixels;
if (imageScale < 1) {
shouldScaleDown = YES;
} else {
shouldScaleDown = NO;
}
return shouldScaleDown;
}
#endif
@end
...@@ -25,7 +25,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { ...@@ -25,7 +25,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageLowPriority = 1 << 1, SDWebImageLowPriority = 1 << 1,
/** /**
* This flag disables on-disk caching after the download finished, only cache in memory * This flag disables on-disk caching
*/ */
SDWebImageCacheMemoryOnly = 1 << 2, SDWebImageCacheMemoryOnly = 1 << 2,
...@@ -87,44 +87,14 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { ...@@ -87,44 +87,14 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance) * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success * Use this flag if you want to manually set the image in the completion when success
*/ */
SDWebImageAvoidAutoSetImage = 1 << 11, SDWebImageAvoidAutoSetImage = 1 << 11
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
* If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated.
*/
SDWebImageScaleDownLargeImages = 1 << 12,
/**
* By default, we do not query disk data when the image is cached in memory. This mask can force to query disk data at the same time.
* This flag is recommend to be used with `SDWebImageQueryDiskSync` to ensure the image is loaded in the same runloop.
*/
SDWebImageQueryDataWhenInMemory = 1 << 13,
/**
* By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously to ensure that image is loaded in the same runloop.
* This flag can avoid flashing during cell reuse if you disable memory cache or in some other cases.
*/
SDWebImageQueryDiskSync = 1 << 14,
/**
* By default, when the cache missed, the image is download from the network. This flag can prevent network to load from cache only.
*/
SDWebImageFromCacheOnly = 1 << 15,
/**
* By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image download from the network. This mask can force to apply view transition for memory and disk cache as well.
*/
SDWebImageForceTransition = 1 << 16
}; };
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL);
typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url); typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL); typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
@class SDWebImageManager; @class SDWebImageManager;
...@@ -141,17 +111,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i ...@@ -141,17 +111,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
* *
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/ */
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL; - (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**
* Controls the complicated logic to mark as failed URLs when download error occur.
* If the delegate implement this method, we will not use the built-in way to mark URL as failed based on error code;
@param imageManager The current `SDWebImageManager`
@param imageURL The url of the image
@param error The download error for the url
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error;
/** /**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory. * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
...@@ -163,7 +123,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i ...@@ -163,7 +123,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
* *
* @return The transformed image object. * @return The transformed image object.
*/ */
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL; - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end @end
...@@ -178,23 +138,23 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i ...@@ -178,23 +138,23 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
* @code * @code
SDWebImageManager *manager = [SDWebImageManager sharedManager]; SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager loadImageWithURL:imageURL [manager downloadImageWithURL:imageURL
options:0 options:0
progress:nil progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) { if (image) {
// do something with image // do something with image
} }
}]; }];
* @endcode * @endcode
*/ */
@interface SDWebImageManager : NSObject @interface SDWebImageManager : NSObject
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate; @property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache; @property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader; @property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
/** /**
* The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can * The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can
...@@ -205,47 +165,27 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; ...@@ -205,47 +165,27 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
* *
* @code * @code
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { [[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString]; return [url absoluteString];
}; }];
* @endcode * @endcode
*/ */
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter; @property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
/**
* The cache serializer is a block used to convert the decoded image, the source downloaded data, to the actual data used for storing to the disk cache. If you return nil, means to generate the data from the image instance, see `SDImageCache`.
* For example, if you are using WebP images and facing the slow decoding time issue when later retriving from disk cache again. You can try to encode the decoded image to JPEG/PNG format to disk cache instead of source downloaded data.
* @note The `image` arg is nonnull, but when you also provide a image transformer and the image is transformed, the `data` arg may be nil, take attention to this case.
* @note This method is called from a global queue in order to not to block the main thread.
* @code
SDWebImageManager.sharedManager.cacheSerializer = ^NSData * _Nullable(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL) {
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
switch (format) {
case SDImageFormatWebP:
return image.images ? data : nil;
default:
return data;
}
};
* @endcode
* The default value is nil. Means we just store the source downloaded data to disk cache.
*/
@property (nonatomic, copy, nullable) SDWebImageCacheSerializerBlock cacheSerializer;
/** /**
* Returns global SDWebImageManager instance. * Returns global SDWebImageManager instance.
* *
* @return SDWebImageManager shared instance * @return SDWebImageManager shared instance
*/ */
+ (nonnull instancetype)sharedManager; + (SDWebImageManager *)sharedManager;
/** /**
* Allows to specify instance of cache and image downloader used with image manager. * Allows to specify instance of cache and image downloader used with image manager.
* @return new instance of `SDWebImageManager` with specified cache and downloader. * @return new instance of `SDWebImageManager` with specified cache and downloader.
*/ */
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER; - (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
/** /**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise. * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
...@@ -253,29 +193,26 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { ...@@ -253,29 +193,26 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
* @param url The URL to the image * @param url The URL to the image
* @param options A mask to specify options to use for this request * @param options A mask to specify options to use for this request
* @param progressBlock A block called while image is downloading * @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. * @param completedBlock A block called when operation has been completed.
* *
* This parameter is required. * This parameter is required.
* *
* This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter. * This block has no return value and takes the requested UIImage as first parameter.
* In case of error the image parameter is nil and the third parameter may contain an NSError. * In case of error the image parameter is nil and the second parameter may contain an NSError.
* *
* The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
* or from the memory cache or from the network. * or from the memory cache or from the network.
* *
* The fith parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is * The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
* downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
* block is called a last time with the full image and the last parameter set to YES. * block is called a last time with the full image and the last parameter set to YES.
* *
* The last parameter is the original image URL
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
*/ */
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock; completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
/** /**
* Saves image to cache for given URL * Saves image to cache for given URL
...@@ -285,7 +222,7 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { ...@@ -285,7 +222,7 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
* *
*/ */
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url; - (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/** /**
* Cancel all current operations * Cancel all current operations
...@@ -298,6 +235,24 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { ...@@ -298,6 +235,24 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
- (BOOL)isRunning; - (BOOL)isRunning;
/** /**
* Check if image has already been cached
*
* @param url image url
*
* @return if the image was already cached
*/
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
* Check if image has already been cached on disk only
*
* @param url image url
*
* @return if the image was already cached (disk only)
*/
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
* Async check if image has already been cached * Async check if image has already been cached
* *
* @param url image url * @param url image url
...@@ -305,8 +260,8 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { ...@@ -305,8 +260,8 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
* *
* @note the completion block is always executed on the main queue * @note the completion block is always executed on the main queue
*/ */
- (void)cachedImageExistsForURL:(nullable NSURL *)url - (void)cachedImageExistsForURL:(NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/** /**
* Async check if image has already been cached on disk only * Async check if image has already been cached on disk only
...@@ -316,13 +271,34 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) { ...@@ -316,13 +271,34 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
* *
* @note the completion block is always executed on the main queue * @note the completion block is always executed on the main queue
*/ */
- (void)diskImageExistsForURL:(nullable NSURL *)url - (void)diskImageExistsForURL:(NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/** /**
*Return the cache key for a given URL *Return the cache key for a given URL
*/ */
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url; - (NSString *)cacheKeyForURL:(NSURL *)url;
@end
#pragma mark - Deprecated
typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionBlock`");
typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionWithFinishedBlock`");
@interface SDWebImageManager (Deprecated)
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @deprecated This method has been deprecated. Use `downloadImageWithURL:options:progress:completed:`
*/
- (id <SDWebImageOperation>)downloadWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletedWithFinishedBlock)completedBlock __deprecated_msg("Method deprecated. Use `downloadImageWithURL:options:progress:completed:`");
@end @end
...@@ -7,35 +7,28 @@ ...@@ -7,35 +7,28 @@
*/ */
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
#import "NSImage+WebCache.h"
#import <objc/message.h> #import <objc/message.h>
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken; @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation; @property (strong, nonatomic) NSOperation *cacheOperation;
@property (weak, nonatomic, nullable) SDWebImageManager *manager;
@end @end
@interface SDWebImageManager () @interface SDWebImageManager ()
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; @property (strong, nonatomic, readwrite) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader; @property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; @property (strong, nonatomic) NSMutableSet *failedURLs;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; // a lock to keep the access to `failedURLs` thread-safe @property (strong, nonatomic) NSMutableArray *runningOperations;
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock; // a lock to keep the access to `runningOperations` thread-safe
@end @end
@implementation SDWebImageManager @implementation SDWebImageManager
+ (nonnull instancetype)sharedManager { + (id)sharedManager {
static dispatch_once_t once; static dispatch_once_t once;
static id instance; static id instance;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
...@@ -44,42 +37,47 @@ ...@@ -44,42 +37,47 @@
return instance; return instance;
} }
- (nonnull instancetype)init { - (instancetype)init {
SDImageCache *cache = [SDImageCache sharedImageCache]; SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader]; return [self initWithCache:cache downloader:downloader];
} }
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { - (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader {
if ((self = [super init])) { if ((self = [super init])) {
_imageCache = cache; _imageCache = cache;
_imageDownloader = downloader; _imageDownloader = downloader;
_failedURLs = [NSMutableSet new]; _failedURLs = [NSMutableSet new];
_failedURLsLock = dispatch_semaphore_create(1); _runningOperations = [NSMutableArray new];
_runningOperations = [NSMutableSet new];
_runningOperationsLock = dispatch_semaphore_create(1);
} }
return self; return self;
} }
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url { - (NSString *)cacheKeyForURL:(NSURL *)url {
if (!url) { if (!url) {
return @""; return @"";
} }
if (self.cacheKeyFilter) { if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url); return self.cacheKeyFilter(url);
} else { } else {
return url.absoluteString; return [url absoluteString];
} }
} }
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image { - (BOOL)cachedImageExistsForURL:(NSURL *)url {
return SDScaledImageForKey(key, image); NSString *key = [self cacheKeyForURL:url];
if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
return [self.imageCache diskImageExistsWithKey:key];
} }
- (void)cachedImageExistsForURL:(nullable NSURL *)url - (BOOL)diskImageExistsForURL:(NSURL *)url {
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url];
return [self.imageCache diskImageExistsWithKey:key];
}
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url]; NSString *key = [self cacheKeyForURL:url];
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil); BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
...@@ -102,8 +100,8 @@ ...@@ -102,8 +100,8 @@
}]; }];
} }
- (void)diskImageExistsForURL:(nullable NSURL *)url - (void)diskImageExistsForURL:(NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url]; NSString *key = [self cacheKeyForURL:url];
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
...@@ -114,14 +112,14 @@ ...@@ -114,14 +112,14 @@
}]; }];
} }
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless // Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) { if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url]; url = [NSURL URLWithString:(NSString *)url];
...@@ -132,48 +130,43 @@ ...@@ -132,48 +130,43 @@
url = nil; url = nil;
} }
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self; __weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO; BOOL isFailedUrl = NO;
if (url) { @synchronized (self.failedURLs) {
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url]; isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
} }
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url]; dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation; return operation;
} }
LOCK(self.runningOperationsLock); @synchronized (self.runningOperations) {
[self.runningOperations addObject:operation]; [self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock); }
NSString *key = [self cacheKeyForURL:url]; NSString *key = [self cacheKeyForURL:url];
SDImageCacheOptions cacheOptions = 0; operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory; if (operation.isCancelled) {
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync; @synchronized (self.runningOperations) {
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages; [self.runningOperations removeObject:operation];
}
__weak SDWebImageCombinedOperation *weakOperation = operation;
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
[self safelyRemoveOperationFromRunning:strongOperation];
return; return;
} }
// Check whether we should download image from network if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly)) if (image && options & SDWebImageRefreshCached) {
&& (!cachedImage || options & SDWebImageRefreshCached) dispatch_main_sync_safe(^{
&& (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
if (shouldDownload) { // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
if (cachedImage && options & SDWebImageRefreshCached) { completedBlock(image, nil, cacheType, YES, url);
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image });
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
} }
// download if no image or requested to refresh anyway, and download allowed by delegate // download if no image or requested to refresh anyway, and download allowed by delegate
...@@ -185,182 +178,198 @@ ...@@ -185,182 +178,198 @@
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; if (image && options & SDWebImageRefreshCached) {
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing // force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing // ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
} }
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
// `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle __strong __typeof(weakOperation) strongOperation = weakOperation;
__weak typeof(strongOperation) weakSubOperation = strongOperation; if (!strongOperation || strongOperation.isCancelled) {
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
if (!strongSubOperation || strongSubOperation.isCancelled) {
// Do nothing if the operation was cancelled // Do nothing if the operation was cancelled
// See #699 for more details // See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) { }
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url]; else if (error) {
BOOL shouldBlockFailedURL; dispatch_main_sync_safe(^{
// Check whether we should block failed url if (strongOperation && !strongOperation.isCancelled) {
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error]; }
} else { });
shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorTimedOut && error.code != NSURLErrorCancelled
&& error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotConnectToHost && error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorNetworkConnectionLost); && error.code != NSURLErrorCannotConnectToHost) {
} @synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
if (shouldBlockFailedURL) { }
LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
UNLOCK(self.failedURLsLock);
} }
} }
else { else {
if ((options & SDWebImageRetryFailed)) { if ((options & SDWebImageRetryFailed)) {
LOCK(self.failedURLsLock); @synchronized (self.failedURLs) {
[self.failedURLs removeObject:url]; [self.failedURLs removeObject:url];
UNLOCK(self.failedURLsLock); }
} }
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
// We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
downloadedImage = [self scaledImageForKey:key image:downloadedImage];
}
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block // Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { }
else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) { if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
NSData *cacheData; [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
// pass nil if the image was transformed, so we can recalculate the data from the image
if (self.cacheSerializer) {
cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
} }
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}); });
} else { }
else {
if (downloadedImage && finished) { if (downloadedImage && finished) {
if (self.cacheSerializer) { [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
});
} else {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
} }
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
} }
} }
if (finished) { if (finished) {
[self safelyRemoveOperationFromRunning:strongSubOperation]; @synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
} }
}]; }];
} else if (cachedImage) { operation.cancelBlock = ^{
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [subOperation cancel];
[self safelyRemoveOperationFromRunning:strongOperation];
} else { @synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else {
// Image not in cache and download disallowed by delegate // Image not in cache and download disallowed by delegate
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; dispatch_main_sync_safe(^{
[self safelyRemoveOperationFromRunning:strongOperation]; __strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
} }
}]; }];
return operation; return operation;
} }
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url { - (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url {
if (image && url) { if (image && url) {
NSString *key = [self cacheKeyForURL:url]; NSString *key = [self cacheKeyForURL:url];
[self.imageCache storeImage:image forKey:key toDisk:YES completion:nil]; [self.imageCache storeImage:image forKey:key toDisk:YES];
} }
} }
- (void)cancelAll { - (void)cancelAll {
LOCK(self.runningOperationsLock); @synchronized (self.runningOperations) {
NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy]; NSArray *copiedOperations = [self.runningOperations copy];
UNLOCK(self.runningOperationsLock); [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array [self.runningOperations removeObjectsInArray:copiedOperations];
}
} }
- (BOOL)isRunning { - (BOOL)isRunning {
BOOL isRunning = NO; BOOL isRunning = NO;
LOCK(self.runningOperationsLock); @synchronized(self.runningOperations) {
isRunning = (self.runningOperations.count > 0); isRunning = (self.runningOperations.count > 0);
UNLOCK(self.runningOperationsLock); }
return isRunning; return isRunning;
} }
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation { @end
if (!operation) {
return;
}
LOCK(self.runningOperationsLock);
[self.runningOperations removeObject:operation];
UNLOCK(self.runningOperationsLock);
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
error:(nullable NSError *)error
url:(nullable NSURL *)url {
[self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation @implementation SDWebImageCombinedOperation
completion:(nullable SDInternalCompletionBlock)completionBlock
image:(nullable UIImage *)image - (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
data:(nullable NSData *)data // check if the operation is already cancelled, then we just call the cancelBlock
error:(nullable NSError *)error if (self.isCancelled) {
cacheType:(SDImageCacheType)cacheType if (cancelBlock) {
finished:(BOOL)finished cancelBlock();
url:(nullable NSURL *)url {
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
} }
}); _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
} }
@end @end
@implementation SDWebImageCombinedOperation @implementation SDWebImageManager (Deprecated)
- (void)cancel { // deprecated method, uses the non deprecated method
@synchronized(self) { // adapter for the completion block
self.cancelled = YES; - (id <SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock {
if (self.cacheOperation) { return [self downloadImageWithURL:url
[self.cacheOperation cancel]; options:options
self.cacheOperation = nil; progress:progressBlock
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (self.downloadToken) { if (completedBlock) {
[self.manager.imageDownloader cancel:self.downloadToken]; completedBlock(image, error, cacheType, finished);
} }
[self.manager safelyRemoveOperationFromRunning:self]; }];
}
} }
@end @end
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
* @param finishedCount The total number of images that were prefetched (successful or not) * @param finishedCount The total number of images that were prefetched (successful or not)
* @param totalCount The total number of images that were to be prefetched * @param totalCount The total number of images that were to be prefetched
*/ */
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(nullable NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount; - (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;
/** /**
* Called when all images are prefetched. * Called when all images are prefetched.
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
* @param totalCount The total number of images that were prefetched (whether successful or not) * @param totalCount The total number of images that were prefetched (whether successful or not)
* @param skippedCount The total number of images that were skipped * @param skippedCount The total number of images that were skipped
*/ */
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount; - (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;
@end @end
...@@ -46,7 +46,7 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, ...@@ -46,7 +46,7 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
/** /**
* The web image manager * The web image manager
*/ */
@property (strong, nonatomic, readonly, nonnull) SDWebImageManager *manager; @property (strong, nonatomic, readonly) SDWebImageManager *manager;
/** /**
* Maximum number of URLs to prefetch at the same time. Defaults to 3. * Maximum number of URLs to prefetch at the same time. Defaults to 3.
...@@ -61,35 +61,33 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, ...@@ -61,35 +61,33 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
/** /**
* Queue options for Prefetcher. Defaults to Main Queue. * Queue options for Prefetcher. Defaults to Main Queue.
*/ */
@property (strong, nonatomic, nonnull) dispatch_queue_t prefetcherQueue; @property (nonatomic, assign) dispatch_queue_t prefetcherQueue;
@property (weak, nonatomic, nullable) id <SDWebImagePrefetcherDelegate> delegate; @property (weak, nonatomic) id <SDWebImagePrefetcherDelegate> delegate;
/** /**
* Return the global image prefetcher instance. * Return the global image prefetcher instance.
*/ */
+ (nonnull instancetype)sharedImagePrefetcher; + (SDWebImagePrefetcher *)sharedImagePrefetcher;
/** /**
* Allows you to instantiate a prefetcher with any arbitrary image manager. * Allows you to instantiate a prefetcher with any arbitrary image manager.
*/ */
- (nonnull instancetype)initWithImageManager:(nonnull SDWebImageManager *)manager NS_DESIGNATED_INITIALIZER; - (id)initWithImageManager:(SDWebImageManager *)manager;
/** /**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching, * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time, * currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list. * and skips images for failed downloads and proceed to the next image in the list
* Any previously-running prefetch operations are canceled.
* *
* @param urls list of URLs to prefetch * @param urls list of URLs to prefetch
*/ */
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls; - (void)prefetchURLs:(NSArray *)urls;
/** /**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching, * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time, * currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list. * and skips images for failed downloads and proceed to the next image in the list
* Any previously-running prefetch operations are canceled.
* *
* @param urls list of URLs to prefetch * @param urls list of URLs to prefetch
* @param progressBlock block to be called when progress updates; * @param progressBlock block to be called when progress updates;
...@@ -99,9 +97,7 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, ...@@ -99,9 +97,7 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
* first param is the number of completed (successful or not) requests, * first param is the number of completed (successful or not) requests,
* second parameter is the number of skipped requests * second parameter is the number of skipped requests
*/ */
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls - (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock;
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock;
/** /**
* Remove and cancel queued list * Remove and cancel queued list
......
...@@ -10,20 +10,20 @@ ...@@ -10,20 +10,20 @@
@interface SDWebImagePrefetcher () @interface SDWebImagePrefetcher ()
@property (strong, nonatomic, nonnull) SDWebImageManager *manager; @property (strong, nonatomic) SDWebImageManager *manager;
@property (strong, atomic, nullable) NSArray<NSURL *> *prefetchURLs; // may be accessed from different queue @property (strong, nonatomic) NSArray *prefetchURLs;
@property (assign, nonatomic) NSUInteger requestedCount; @property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount; @property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount; @property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime; @property (assign, nonatomic) NSTimeInterval startedTime;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherCompletionBlock completionBlock; @property (copy, nonatomic) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherProgressBlock progressBlock; @property (copy, nonatomic) SDWebImagePrefetcherProgressBlock progressBlock;
@end @end
@implementation SDWebImagePrefetcher @implementation SDWebImagePrefetcher
+ (nonnull instancetype)sharedImagePrefetcher { + (SDWebImagePrefetcher *)sharedImagePrefetcher {
static dispatch_once_t once; static dispatch_once_t once;
static id instance; static id instance;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
...@@ -32,11 +32,11 @@ ...@@ -32,11 +32,11 @@
return instance; return instance;
} }
- (nonnull instancetype)init { - (id)init {
return [self initWithImageManager:[SDWebImageManager new]]; return [self initWithImageManager:[SDWebImageManager new]];
} }
- (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager { - (id)initWithImageManager:(SDWebImageManager *)manager {
if ((self = [super init])) { if ((self = [super init])) {
_manager = manager; _manager = manager;
_options = SDWebImageLowPriority; _options = SDWebImageLowPriority;
...@@ -55,33 +55,33 @@ ...@@ -55,33 +55,33 @@
} }
- (void)startPrefetchingAtIndex:(NSUInteger)index { - (void)startPrefetchingAtIndex:(NSUInteger)index {
NSURL *currentURL; if (index >= self.prefetchURLs.count) return;
@synchronized(self) { self.requestedCount++;
if (index >= self.prefetchURLs.count) return; [self.manager downloadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
currentURL = self.prefetchURLs[index];
self.requestedCount++;
}
[self.manager loadImageWithURL:currentURL options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!finished) return; if (!finished) return;
self.finishedCount++; self.finishedCount++;
if (self.progressBlock) { if (image) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count); if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
} }
if (!image) { else {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
// Add last failed // Add last failed
self.skippedCount++; self.skippedCount++;
} }
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) { if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
[self.delegate imagePrefetcher:self [self.delegate imagePrefetcher:self
didPrefetchURL:currentURL didPrefetchURL:self.prefetchURLs[index]
finishedCount:self.finishedCount finishedCount:self.finishedCount
totalCount:self.prefetchURLs.count totalCount:self.prefetchURLs.count
]; ];
} }
if (self.prefetchURLs.count > self.requestedCount) { if (self.prefetchURLs.count > self.requestedCount) {
dispatch_async(self.prefetcherQueue, ^{ dispatch_async(self.prefetcherQueue, ^{
// we need dispatch to avoid function recursion call. This can prevent stack overflow even for huge urls list
[self startPrefetchingAtIndex:self.requestedCount]; [self startPrefetchingAtIndex:self.requestedCount];
}); });
} else if (self.finishedCount == self.requestedCount) { } else if (self.finishedCount == self.requestedCount) {
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
} }
- (void)reportStatus { - (void)reportStatus {
NSUInteger total = (self.prefetchURLs).count; NSUInteger total = [self.prefetchURLs count];
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) { if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
[self.delegate imagePrefetcher:self [self.delegate imagePrefetcher:self
didFinishWithTotalCount:(total - self.skippedCount) didFinishWithTotalCount:(total - self.skippedCount)
...@@ -105,13 +105,11 @@ ...@@ -105,13 +105,11 @@
} }
} }
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls { - (void)prefetchURLs:(NSArray *)urls {
[self prefetchURLs:urls progress:nil completed:nil]; [self prefetchURLs:urls progress:nil completed:nil];
} }
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls - (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
[self cancelPrefetching]; // Prevent duplicate prefetch request [self cancelPrefetching]; // Prevent duplicate prefetch request
self.startedTime = CFAbsoluteTimeGetCurrent(); self.startedTime = CFAbsoluteTimeGetCurrent();
self.prefetchURLs = urls; self.prefetchURLs = urls;
...@@ -132,12 +130,10 @@ ...@@ -132,12 +130,10 @@
} }
- (void)cancelPrefetching { - (void)cancelPrefetching {
@synchronized(self) { self.prefetchURLs = nil;
self.prefetchURLs = nil; self.skippedCount = 0;
self.skippedCount = 0; self.requestedCount = 0;
self.requestedCount = 0; self.finishedCount = 0;
self.finishedCount = 0;
}
[self.manager cancelAll]; [self.manager cancelAll];
} }
......
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#if SD_UIKIT || SD_MAC
#import "SDImageCache.h"
// This class is used to provide a transition animation after the view category load image finished. Use this on `sd_imageTransition` in UIView+WebCache.h
// for UIKit(iOS & tvOS), we use `+[UIView transitionWithView:duration:options:animations:completion]` for transition animation.
// for AppKit(macOS), we use `+[NSAnimationContext runAnimationGroup:completionHandler:]` for transition animation. You can call `+[NSAnimationContext currentContext]` to grab the context during animations block.
// These transition are provided for basic usage. If you need complicated animation, consider to directly use Core Animation or use `SDWebImageAvoidAutoSetImage` and implement your own after image load finished.
#if SD_UIKIT
typedef UIViewAnimationOptions SDWebImageAnimationOptions;
#else
typedef NS_OPTIONS(NSUInteger, SDWebImageAnimationOptions) {
SDWebImageAnimationOptionAllowsImplicitAnimation = 1 << 0, // specify `allowsImplicitAnimation` for the `NSAnimationContext`
};
#endif
typedef void (^SDWebImageTransitionPreparesBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
typedef void (^SDWebImageTransitionAnimationsBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image);
typedef void (^SDWebImageTransitionCompletionBlock)(BOOL finished);
@interface SDWebImageTransition : NSObject
/**
By default, we set the image to the view at the beginning of the animtions. You can disable this and provide custom set image process
*/
@property (nonatomic, assign) BOOL avoidAutoSetImage;
/**
The duration of the transition animation, measured in seconds. Defaults to 0.5.
*/
@property (nonatomic, assign) NSTimeInterval duration;
/**
The timing function used for all animations within this transition animation (macOS).
*/
@property (nonatomic, strong, nullable) CAMediaTimingFunction *timingFunction NS_AVAILABLE_MAC(10_7);
/**
A mask of options indicating how you want to perform the animations.
*/
@property (nonatomic, assign) SDWebImageAnimationOptions animationOptions;
/**
A block object to be executed before the animation sequence starts.
*/
@property (nonatomic, copy, nullable) SDWebImageTransitionPreparesBlock prepares;
/**
A block object that contains the changes you want to make to the specified view.
*/
@property (nonatomic, copy, nullable) SDWebImageTransitionAnimationsBlock animations;
/**
A block object to be executed when the animation sequence ends.
*/
@property (nonatomic, copy, nullable) SDWebImageTransitionCompletionBlock completion;
@end
// Convenience way to create transition. Remember to specify the duration if needed.
// for UIKit, these transition just use the correspond `animationOptions`. By default we enable `UIViewAnimationOptionAllowUserInteraction` to allow user interaction during transition.
// for AppKit, these transition use Core Animation in `animations`. So your view must be layer-backed. Set `wantsLayer = YES` before you apply it.
@interface SDWebImageTransition (Conveniences)
// class property is available in Xcode 8. We will drop the Xcode 7.3 support in 5.x
#if __has_feature(objc_class_property)
/// Fade transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *fadeTransition;
/// Flip from left transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromLeftTransition;
/// Flip from right transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromRightTransition;
/// Flip from top transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromTopTransition;
/// Flip from bottom transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromBottomTransition;
/// Curl up transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlUpTransition;
/// Curl down transition.
@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlDownTransition;
#else
+ (nonnull instancetype)fadeTransition;
+ (nonnull instancetype)flipFromLeftTransition;
+ (nonnull instancetype)flipFromRightTransition;
+ (nonnull instancetype)flipFromTopTransition;
+ (nonnull instancetype)flipFromBottomTransition;
+ (nonnull instancetype)curlUpTransition;
+ (nonnull instancetype)curlDownTransition;
#endif
@end
#endif
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageTransition.h"
#if SD_UIKIT || SD_MAC
#if SD_MAC
#import <QuartzCore/QuartzCore.h>
#endif
@implementation SDWebImageTransition
- (instancetype)init {
self = [super init];
if (self) {
self.duration = 0.5;
}
return self;
}
@end
@implementation SDWebImageTransition (Conveniences)
+ (SDWebImageTransition *)fadeTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionFade;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)flipFromLeftTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionPush;
trans.subtype = kCATransitionFromLeft;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)flipFromRightTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionPush;
trans.subtype = kCATransitionFromRight;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)flipFromTopTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionPush;
trans.subtype = kCATransitionFromTop;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)flipFromBottomTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionPush;
trans.subtype = kCATransitionFromBottom;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)curlUpTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionReveal;
trans.subtype = kCATransitionFromTop;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
+ (SDWebImageTransition *)curlDownTransition {
SDWebImageTransition *transition = [SDWebImageTransition new];
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction;
#else
transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) {
CATransition *trans = [CATransition animation];
trans.type = kCATransitionReveal;
trans.subtype = kCATransitionFromBottom;
[view.layer addAnimation:trans forKey:kCATransition];
};
#endif
return transition;
}
@end
#endif
...@@ -7,9 +7,6 @@ ...@@ -7,9 +7,6 @@
*/ */
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#if SD_UIKIT
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
/** /**
...@@ -17,19 +14,17 @@ ...@@ -17,19 +14,17 @@
*/ */
@interface UIButton (WebCache) @interface UIButton (WebCache)
#pragma mark - Image
/** /**
* Get the current image URL. * Get the current image URL.
*/ */
- (nullable NSURL *)sd_currentImageURL; - (NSURL *)sd_currentImageURL;
/** /**
* Get the image URL for a control state. * Get the image URL for a control state.
* *
* @param state Which state you want to know the URL for. The values are described in UIControlState. * @param state Which state you want to know the URL for. The values are described in UIControlState.
*/ */
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state; - (NSURL *)sd_imageURLForState:(UIControlState)state;
/** /**
* Set the imageView `image` with an `url`. * Set the imageView `image` with an `url`.
...@@ -39,8 +34,7 @@ ...@@ -39,8 +34,7 @@
* @param url The url for the image. * @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState. * @param state The state that uses the specified title. The values are described in UIControlState.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state;
forState:(UIControlState)state NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url` and a placeholder. * Set the imageView `image` with an `url` and a placeholder.
...@@ -52,9 +46,7 @@ ...@@ -52,9 +46,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options: * @see sd_setImageWithURL:placeholderImage:options:
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`, placeholder and custom options. * Set the imageView `image` with an `url`, placeholder and custom options.
...@@ -66,10 +58,7 @@ ...@@ -66,10 +58,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`. * Set the imageView `image` with an `url`.
...@@ -84,9 +73,7 @@ ...@@ -84,9 +73,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the imageView `image` with an `url`, placeholder. * Set the imageView `image` with an `url`, placeholder.
...@@ -102,10 +89,7 @@ ...@@ -102,10 +89,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`, placeholder and custom options. * Set the imageView `image` with an `url`, placeholder and custom options.
...@@ -122,25 +106,7 @@ ...@@ -122,25 +106,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Background Image
/**
* Get the current background image URL.
*/
- (nullable NSURL *)sd_currentBackgroundImageURL;
/**
* Get the background image URL for a control state.
*
* @param state Which state you want to know the URL for. The values are described in UIControlState.
*/
- (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state;
/** /**
* Set the backgroundImageView `image` with an `url`. * Set the backgroundImageView `image` with an `url`.
...@@ -150,8 +116,7 @@ ...@@ -150,8 +116,7 @@
* @param url The url for the image. * @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState. * @param state The state that uses the specified title. The values are described in UIControlState.
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state;
forState:(UIControlState)state NS_REFINED_FOR_SWIFT;
/** /**
* Set the backgroundImageView `image` with an `url` and a placeholder. * Set the backgroundImageView `image` with an `url` and a placeholder.
...@@ -163,9 +128,7 @@ ...@@ -163,9 +128,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options: * @see sd_setImageWithURL:placeholderImage:options:
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/** /**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options. * Set the backgroundImageView `image` with an `url`, placeholder and custom options.
...@@ -177,10 +140,7 @@ ...@@ -177,10 +140,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/** /**
* Set the backgroundImageView `image` with an `url`. * Set the backgroundImageView `image` with an `url`.
...@@ -195,9 +155,7 @@ ...@@ -195,9 +155,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the backgroundImageView `image` with an `url`, placeholder. * Set the backgroundImageView `image` with an `url`, placeholder.
...@@ -213,10 +171,7 @@ ...@@ -213,10 +171,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/** /**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options. * Set the backgroundImageView `image` with an `url`, placeholder and custom options.
...@@ -232,13 +187,7 @@ ...@@ -232,13 +187,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
#pragma mark - Cancel
/** /**
* Cancel the current image download * Cancel the current image download
...@@ -252,4 +201,29 @@ ...@@ -252,4 +201,29 @@
@end @end
#endif
@interface UIButton (WebCacheDeprecated)
- (NSURL *)currentImageURL __deprecated_msg("Use `sd_currentImageURL`");
- (NSURL *)imageURLForState:(UIControlState)state __deprecated_msg("Use `sd_imageURLForState:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:completed:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:completed:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:completed:`");
- (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelImageLoadForState:`");
- (void)cancelBackgroundImageLoadForState:(UIControlState)state __deprecated_msg("Use `sd_cancelBackgroundImageLoadForState:`");
@end
...@@ -7,168 +7,165 @@ ...@@ -7,168 +7,165 @@
*/ */
#import "UIButton+WebCache.h" #import "UIButton+WebCache.h"
#if SD_UIKIT
#import "objc/runtime.h" #import "objc/runtime.h"
#import "UIView+WebCacheOperation.h" #import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
static char imageURLStorageKey; static char imageURLStorageKey;
typedef NSMutableDictionary<NSString *, NSURL *> SDStateImageURLDictionary;
static inline NSString * imageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
}
static inline NSString * backgroundImageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state];
}
static inline NSString * imageOperationKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state];
}
static inline NSString * backgroundImageOperationKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state];
}
@implementation UIButton (WebCache) @implementation UIButton (WebCache)
#pragma mark - Image - (NSURL *)sd_currentImageURL {
NSURL *url = self.imageURLStorage[@(self.state)];
- (nullable NSURL *)sd_currentImageURL {
NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)];
if (!url) { if (!url) {
url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)]; url = self.imageURLStorage[@(UIControlStateNormal)];
} }
return url; return url;
} }
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state { - (NSURL *)sd_imageURLForState:(UIControlState)state {
return self.sd_imageURLStorage[imageURLKeyForState(state)]; return self.imageURLStorage[@(state)];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder { - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder [self setImage:placeholder forState:state];
options:(SDWebImageOptions)options [self sd_cancelImageLoadForState:state];
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)];
} else {
self.sd_imageURLStorage[imageURLKeyForState(state)] = url;
}
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:imageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
#pragma mark - Background Image
- (nullable NSURL *)sd_currentBackgroundImageURL {
NSURL *url = self.sd_imageURLStorage[backgroundImageURLKeyForState(self.state)];
if (!url) { if (!url) {
url = self.sd_imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)]; [self.imageURLStorage removeObjectForKey:@(state)];
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
return;
} }
return url; self.imageURLStorage[@(state)] = url;
}
__weak __typeof(self)wself = self;
- (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state { id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
return self.sd_imageURLStorage[backgroundImageURLKeyForState(state)]; if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIButton *sself = wself;
if (!sself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
[sself setImage:image forState:state];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forState:state];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder { - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
} }
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
forState:(UIControlState)state [self sd_cancelBackgroundImageLoadForState:state];
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options [self setBackgroundImage:placeholder forState:state];
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) { if (url) {
[self.sd_imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)]; __weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIButton *sself = wself;
if (!sself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
[sself setBackgroundImage:image forState:state];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setBackgroundImageLoadOperation:operation forState:state];
} else { } else {
self.sd_imageURLStorage[backgroundImageURLKeyForState(state)] = url; dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
} }
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:backgroundImageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setBackgroundImage:image forState:state];
}
progress:nil
completed:completedBlock];
} }
#pragma mark - Cancel - (void)sd_setImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_cancelImageLoadForState:(UIControlState)state { - (void)sd_cancelImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)]; [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
} }
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state { - (void)sd_setBackgroundImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)]; [self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
} }
#pragma mark - Private - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (SDStateImageURLDictionary *)sd_imageURLStorage { - (NSMutableDictionary *)imageURLStorage {
SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey); NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage) { if (!storage)
{
storage = [NSMutableDictionary dictionary]; storage = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} }
...@@ -178,4 +175,96 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat ...@@ -178,4 +175,96 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat
@end @end
#endif
@implementation UIButton (WebCacheDeprecated)
- (NSURL *)currentImageURL {
return [self sd_currentImageURL];
}
- (NSURL *)imageURLForState:(UIControlState)state {
return [self sd_imageURLForState:state];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)cancelCurrentImageLoad {
// in a backwards compatible manner, cancel for current state
[self sd_cancelImageLoadForState:self.state];
}
- (void)cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelBackgroundImageLoadForState:state];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImage+ForceDecode.h"
#import "SDWebImageCodersManager.h"
@implementation UIImage (ForceDecode)
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
if (!image) {
return nil;
}
NSData *tempData;
return [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&tempData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image {
if (!image) {
return nil;
}
NSData *tempData;
return [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&tempData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(YES)}];
}
@end
/* //
* This file is part of the SDWebImage package. // UIImage+GIF.h
* (c) Olivier Poitrey <rs@dailymotion.com> // LBGIFImage
* (c) Laurin Brandner //
* // Created by Laurin Brandner on 06.01.12.
* For the full copyright and license information, please view the LICENSE // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
* file that was distributed with this source code. //
*/
#import "SDWebImageCompat.h" #import <UIKit/UIKit.h>
@interface UIImage (GIF) @interface UIImage (GIF)
/** + (UIImage *)sd_animatedGIFNamed:(NSString *)name;
* Creates an animated UIImage from an NSData.
* For static GIF, will create an UIImage with `images` array set to nil. For animated GIF, will create an UIImage with valid `images` array.
*/
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data; + (UIImage *)sd_animatedGIFWithData:(NSData *)data;
/** - (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;
* Checks if an UIImage instance is a GIF. Will use the `images` array.
*/
- (BOOL)isGIF;
@end @end
/* //
* This file is part of the SDWebImage package. // UIImage+GIF.m
* (c) Olivier Poitrey <rs@dailymotion.com> // LBGIFImage
* (c) Laurin Brandner //
* // Created by Laurin Brandner on 06.01.12.
* For the full copyright and license information, please view the LICENSE // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
* file that was distributed with this source code. //
*/
#import "UIImage+GIF.h" #import "UIImage+GIF.h"
#import "SDWebImageGIFCoder.h" #import <ImageIO/ImageIO.h>
#import "NSImage+WebCache.h"
@implementation UIImage (GIF) @implementation UIImage (GIF)
...@@ -17,11 +15,147 @@ ...@@ -17,11 +15,147 @@
if (!data) { if (!data) {
return nil; return nil;
} }
return [[SDWebImageGIFCoder sharedCoder] decodedImageWithData:data];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
continue;
}
duration += [self sd_frameDurationAtIndex:i source:source];
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image);
}
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
}
CFRelease(source);
return animatedImage;
}
+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
}
else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
// for more information.
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
} }
- (BOOL)isGIF { + (UIImage *)sd_animatedGIFNamed:(NSString *)name {
return (self.images != nil); CGFloat scale = [UIScreen mainScreen].scale;
if (scale > 1.0f) {
NSString *retinaPath = [[NSBundle mainBundle] pathForResource:[name stringByAppendingString:@"@2x"] ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:retinaPath];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
data = [NSData dataWithContentsOfFile:path];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
return [UIImage imageNamed:name];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
return [UIImage imageNamed:name];
}
}
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size {
if (CGSizeEqualToSize(self.size, size) || CGSizeEqualToSize(size, CGSizeZero)) {
return self;
}
CGSize scaledSize = size;
CGPoint thumbnailPoint = CGPointZero;
CGFloat widthFactor = size.width / self.size.width;
CGFloat heightFactor = size.height / self.size.height;
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor;
scaledSize.width = self.size.width * scaleFactor;
scaledSize.height = self.size.height * scaleFactor;
if (widthFactor > heightFactor) {
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
}
else if (widthFactor < heightFactor) {
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
}
NSMutableArray *scaledImages = [NSMutableArray array];
for (UIImage *image in self.images) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[scaledImages addObject:newImage];
UIGraphicsEndImageContext();
}
return [UIImage animatedImageWithImages:scaledImages duration:self.duration];
} }
@end @end
/* //
* This file is part of the SDWebImage package. // UIImage+MultiFormat.h
* (c) Olivier Poitrey <rs@dailymotion.com> // SDWebImage
* //
* For the full copyright and license information, please view the LICENSE // Created by Olivier Poitrey on 07/06/13.
* file that was distributed with this source code. // Copyright (c) 2013 Dailymotion. All rights reserved.
*/ //
#import "SDWebImageCompat.h" #import <UIKit/UIKit.h>
#import "NSData+ImageContentType.h"
@interface UIImage (MultiFormat) @interface UIImage (MultiFormat)
/** + (UIImage *)sd_imageWithData:(NSData *)data;
* UIKit:
* For static image format, this value is always 0.
* For animated image format, 0 means infinite looping.
* @note Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
* AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage.
* The getter of this property will get the loop count from GIF imageRep
* The setter of this property will set the loop count from GIF imageRep
*/
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;
/**
* The image format represent the original compressed image data format.
* If you don't manually specify a format, this information is retrieve from CGImage using `CGImageGetUTType`, which may return nil for non-CG based image. At this time it will return `SDImageFormatUndefined` as default value.
* @note Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
*/
@property (nonatomic, assign) SDImageFormat sd_imageFormat;
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data;
- (nullable NSData *)sd_imageData;
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat;
@end @end
/* //
* This file is part of the SDWebImage package. // UIImage+MultiFormat.m
* (c) Olivier Poitrey <rs@dailymotion.com> // SDWebImage
* //
* For the full copyright and license information, please view the LICENSE // Created by Olivier Poitrey on 07/06/13.
* file that was distributed with this source code. // Copyright (c) 2013 Dailymotion. All rights reserved.
*/ //
#import "UIImage+MultiFormat.h" #import "UIImage+MultiFormat.h"
#import "NSImage+WebCache.h" #import "UIImage+GIF.h"
#import "SDWebImageCodersManager.h" #import "NSData+ImageContentType.h"
#import "objc/runtime.h" #import <ImageIO/ImageIO.h>
#ifdef SD_WEBP
#import "UIImage+WebP.h"
#endif
@implementation UIImage (MultiFormat) @implementation UIImage (MultiFormat)
#if SD_MAC + (UIImage *)sd_imageWithData:(NSData *)data {
- (NSUInteger)sd_imageLoopCount { if (!data) {
NSUInteger imageLoopCount = 0; return nil;
for (NSImageRep *rep in self.representations) { }
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep; UIImage *image;
imageLoopCount = [[bitmapRep valueForProperty:NSImageLoopCount] unsignedIntegerValue]; NSString *imageContentType = [NSData sd_contentTypeForImageData:data];
break; if ([imageContentType isEqualToString:@"image/gif"]) {
image = [UIImage sd_animatedGIFWithData:data];
}
#ifdef SD_WEBP
else if ([imageContentType isEqualToString:@"image/webp"])
{
image = [UIImage sd_imageWithWebPData:data];
}
#endif
else {
image = [[UIImage alloc] initWithData:data];
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
} }
} }
return imageLoopCount;
return image;
} }
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
for (NSImageRep *rep in self.representations) { +(UIImageOrientation)sd_imageOrientationFromImageData:(NSData *)imageData {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) { UIImageOrientation result = UIImageOrientationUp;
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
[bitmapRep setProperty:NSImageLoopCount withValue:@(sd_imageLoopCount)]; if (imageSource) {
break; CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
CFTypeRef val;
int exifOrientation;
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) {
CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);
result = [self sd_exifOrientationToiOSOrientation:exifOrientation];
} // else - if it's not set it remains at up
CFRelease((CFTypeRef) properties);
} else {
//NSLog(@"NO PROPERTIES, FAIL");
} }
CFRelease(imageSource);
} }
return result;
} }
#else #pragma mark EXIF orientation tag converter
// Convert an EXIF image orientation to an iOS one.
// reference see here: http://sylvana.net/jpegcrop/exif_orientation.html
+ (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation {
UIImageOrientation orientation = UIImageOrientationUp;
switch (exifOrientation) {
case 1:
orientation = UIImageOrientationUp;
break;
- (NSUInteger)sd_imageLoopCount { case 3:
NSUInteger imageLoopCount = 0; orientation = UIImageOrientationDown;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageLoopCount)); break;
if ([value isKindOfClass:[NSNumber class]]) {
imageLoopCount = value.unsignedIntegerValue;
}
return imageLoopCount;
}
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { case 8:
NSNumber *value = @(sd_imageLoopCount); orientation = UIImageOrientationLeft;
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); break;
}
#endif
- (SDImageFormat)sd_imageFormat { case 6:
SDImageFormat imageFormat = SDImageFormatUndefined; orientation = UIImageOrientationRight;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageFormat)); break;
if ([value isKindOfClass:[NSNumber class]]) {
imageFormat = value.integerValue;
return imageFormat;
}
// Check CGImage's UTType, may return nil for non-Image/IO based image
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
if (&CGImageGetUTType != NULL) {
CFStringRef uttype = CGImageGetUTType(self.CGImage);
imageFormat = [NSData sd_imageFormatFromUTType:uttype];
}
#pragma clang diagnostic pop
return imageFormat;
}
- (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat { case 2:
objc_setAssociatedObject(self, @selector(sd_imageFormat), @(sd_imageFormat), OBJC_ASSOCIATION_RETAIN_NONATOMIC); orientation = UIImageOrientationUpMirrored;
} break;
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { case 4:
return [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data]; orientation = UIImageOrientationDownMirrored;
} break;
- (nullable NSData *)sd_imageData { case 5:
return [self sd_imageDataAsFormat:SDImageFormatUndefined]; orientation = UIImageOrientationLeftMirrored;
} break;
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat { case 7:
NSData *imageData = nil; orientation = UIImageOrientationRightMirrored;
if (self) { break;
imageData = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:self format:imageFormat]; default:
break;
} }
return imageData; return orientation;
} }
@end @end
...@@ -6,10 +6,8 @@ ...@@ -6,10 +6,8 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
#import <UIKit/UIKit.h>
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#if SD_UIKIT
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
/** /**
...@@ -24,7 +22,7 @@ ...@@ -24,7 +22,7 @@
* *
* @param url The url for the image. * @param url The url for the image.
*/ */
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; - (void)sd_setHighlightedImageWithURL:(NSURL *)url;
/** /**
* Set the imageView `highlightedImage` with an `url` and custom options. * Set the imageView `highlightedImage` with an `url` and custom options.
...@@ -34,8 +32,7 @@ ...@@ -34,8 +32,7 @@
* @param url The url for the image. * @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/ */
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `highlightedImage` with an `url`. * Set the imageView `highlightedImage` with an `url`.
...@@ -49,8 +46,7 @@ ...@@ -49,8 +46,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url - (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `highlightedImage` with an `url` and custom options. * Set the imageView `highlightedImage` with an `url` and custom options.
...@@ -65,9 +61,7 @@ ...@@ -65,9 +61,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the imageView `highlightedImage` with an `url` and custom options. * Set the imageView `highlightedImage` with an `url` and custom options.
...@@ -77,18 +71,30 @@ ...@@ -77,18 +71,30 @@
* @param url The url for the image. * @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading * @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value * @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter * and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean * is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock /**
completed:(nullable SDExternalCompletionBlock)completedBlock; * Cancel the current download
*/
- (void)sd_cancelCurrentHighlightedImageLoad;
@end @end
#endif
@interface UIImageView (HighlightedWebCacheDeprecated)
- (void)setHighlightedImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:`");
- (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:completed:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:completed:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:progress:completed:`");
- (void)cancelCurrentHighlightedImageLoad __deprecated_msg("Use `sd_cancelCurrentHighlightedImageLoad`");
@end
...@@ -7,46 +7,106 @@ ...@@ -7,46 +7,106 @@
*/ */
#import "UIImageView+HighlightedWebCache.h" #import "UIImageView+HighlightedWebCache.h"
#if SD_UIKIT
#import "UIView+WebCacheOperation.h" #import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
#define UIImageViewHighlightedWebCacheOperationKey @"highlightedImage"
@implementation UIImageView (HighlightedWebCache) @implementation UIImageView (HighlightedWebCache)
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url { - (void)sd_setHighlightedImageWithURL:(NSURL *)url {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil]; [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil];
} }
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options { - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil]; [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil];
} }
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock]; [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock];
} }
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock]; [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock];
} }
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
options:(SDWebImageOptions)options [self sd_cancelCurrentHighlightedImageLoad];
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock { if (url) {
__weak typeof(self)weakSelf = self; __weak __typeof(self)wself = self;
[self sd_internalSetImageWithURL:url id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
placeholderImage:nil if (!wself) return;
options:options dispatch_main_sync_safe (^
operationKey:@"UIImageViewImageOperationHighlighted" {
setImageBlock:^(UIImage *image, NSData *imageData) { if (!wself) return;
weakSelf.highlightedImage = image; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
} {
progress:progressBlock completedBlock(image, error, cacheType, url);
completed:completedBlock]; return;
}
else if (image) {
wself.highlightedImage = image;
[wself setNeedsLayout];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:UIImageViewHighlightedWebCacheOperationKey];
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_cancelCurrentHighlightedImageLoad {
[self sd_cancelImageLoadOperationWithKey:UIImageViewHighlightedWebCacheOperationKey];
} }
@end @end
#endif
@implementation UIImageView (HighlightedWebCacheDeprecated)
- (void)setHighlightedImageWithURL:(NSURL *)url {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil];
}
- (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)cancelCurrentHighlightedImageLoad {
[self sd_cancelCurrentHighlightedImageLoad];
}
@end
...@@ -27,7 +27,8 @@ ...@@ -27,7 +27,8 @@
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) { if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]; cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]
autorelease];
} }
// Here we use the provided sd_setImageWithURL: method to load the web image // Here we use the provided sd_setImageWithURL: method to load the web image
...@@ -44,13 +45,21 @@ ...@@ -44,13 +45,21 @@
@interface UIImageView (WebCache) @interface UIImageView (WebCache)
/** /**
* Get the current image URL.
*
* Note that because of the limitations of categories this property can get out of sync
* if you use sd_setImage: directly.
*/
- (NSURL *)sd_imageURL;
/**
* Set the imageView `image` with an `url`. * Set the imageView `image` with an `url`.
* *
* The download is asynchronous and cached. * The download is asynchronous and cached.
* *
* @param url The url for the image. * @param url The url for the image.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; - (void)sd_setImageWithURL:(NSURL *)url;
/** /**
* Set the imageView `image` with an `url` and a placeholder. * Set the imageView `image` with an `url` and a placeholder.
...@@ -61,8 +70,7 @@ ...@@ -61,8 +70,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options: * @see sd_setImageWithURL:placeholderImage:options:
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`, placeholder and custom options. * Set the imageView `image` with an `url`, placeholder and custom options.
...@@ -73,9 +81,7 @@ ...@@ -73,9 +81,7 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`. * Set the imageView `image` with an `url`.
...@@ -89,8 +95,7 @@ ...@@ -89,8 +95,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the imageView `image` with an `url`, placeholder. * Set the imageView `image` with an `url`, placeholder.
...@@ -105,9 +110,7 @@ ...@@ -105,9 +110,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/** /**
* Set the imageView `image` with an `url`, placeholder and custom options. * Set the imageView `image` with an `url`, placeholder and custom options.
...@@ -123,10 +126,7 @@ ...@@ -123,10 +126,7 @@
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the imageView `image` with an `url`, placeholder and custom options. * Set the imageView `image` with an `url`, placeholder and custom options.
...@@ -137,23 +137,16 @@ ...@@ -137,23 +137,16 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading * @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value * @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter * and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean * is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
*/ */
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/** /**
* Set the imageView `image` with an `url` and custom options. The placeholder image is from previous cached image and will use the provided one instead if the query failed. * Set the imageView `image` with an `url` and optionally a placeholder image.
* This method was designed to ensure that placeholder and query cache process happened in the same runloop to avoid flashing on cell during two `setImage:` call. But it's really misunderstanding and deprecated.
* This can be done by using `sd_setImageWithURL:` with `SDWebImageQueryDiskSync`. But take care that if the memory cache missed, query disk cache synchronously may reduce the frame rate
* *
* The download is asynchronous and cached. * The download is asynchronous and cached.
* *
...@@ -161,33 +154,62 @@ ...@@ -161,33 +154,62 @@
* @param placeholder The image to be set initially, until the image request finishes. * @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading * @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value * @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter * and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean * is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network. * indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url. * The fourth parameter is the original image url.
* @deprecated consider using `SDWebImageQueryDiskSync` options with `sd_setImageWithURL:` instead
*/ */
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock __deprecated_msg("This method is misunderstanding and deprecated, consider using `SDWebImageQueryDiskSync` options with `sd_setImageWithURL:` instead");
#if SD_UIKIT
#pragma mark - Animation of multiple images
/** /**
* Download an array of images and starts them in an animation loop * Download an array of images and starts them in an animation loop
* *
* @param arrayOfURLs An array of NSURL * @param arrayOfURLs An array of NSURL
*/ */
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs; - (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
/**
* Cancel the current download
*/
- (void)sd_cancelCurrentImageLoad;
- (void)sd_cancelCurrentAnimationImagesLoad; - (void)sd_cancelCurrentAnimationImagesLoad;
#endif /**
* Show activity UIActivityIndicatorView
*/
- (void)setShowActivityIndicatorView:(BOOL)show;
/**
* set desired UIActivityIndicatorViewStyle
*
* @param style The style of the UIActivityIndicatorView
*/
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
@end
@interface UIImageView (WebCacheDeprecated)
- (NSURL *)imageURL __deprecated_msg("Use `sd_imageURL`");
- (void)setImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options`");
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:progress:completed:`");
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url andPlaceholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithPreviousCachedImageWithURL:placeholderImage:options:progress:completed:`");
- (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs __deprecated_msg("Use `sd_setAnimationImagesWithURLs:`");
- (void)cancelCurrentArrayLoad __deprecated_msg("Use `sd_cancelCurrentAnimationImagesLoad`");
- (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelCurrentImageLoad`");
@end @end
...@@ -9,89 +9,121 @@ ...@@ -9,89 +9,121 @@
#import "UIImageView+WebCache.h" #import "UIImageView+WebCache.h"
#import "objc/runtime.h" #import "objc/runtime.h"
#import "UIView+WebCacheOperation.h" #import "UIView+WebCacheOperation.h"
#import "UIView+WebCache.h"
static char imageURLKey;
static char TAG_ACTIVITY_INDICATOR;
static char TAG_ACTIVITY_STYLE;
static char TAG_ACTIVITY_SHOW;
@implementation UIImageView (WebCache) @implementation UIImageView (WebCache)
- (void)sd_setImageWithURL:(nullable NSURL *)url { - (void)sd_setImageWithURL:(NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
} }
- (void)sd_setImageWithURL:(nullable NSURL *)url - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
placeholderImage:(nullable UIImage *)placeholder [self sd_cancelCurrentImageLoad];
options:(SDWebImageOptions)options objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock { if (!(options & SDWebImageDelayPlaceholder)) {
[self sd_internalSetImageWithURL:url dispatch_main_async_safe(^{
placeholderImage:placeholder self.image = placeholder;
options:options });
operationKey:nil }
setImageBlock:nil
progress:progressBlock if (url) {
completed:completedBlock];
} // check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url [self addActivityIndicator];
placeholderImage:(nullable UIImage *)placeholder }
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock __weak __typeof(self)wself = self;
completed:(nullable SDExternalCompletionBlock)completedBlock { id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url]; NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key]; UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock]; [self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
} }
#if SD_UIKIT - (NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
#pragma mark - Animation of multiple images }
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs { - (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
[self sd_cancelCurrentAnimationImagesLoad]; [self sd_cancelCurrentAnimationImagesLoad];
NSPointerArray *operationsArray = [self sd_animationOperationArray]; __weak __typeof(self)wself = self;
[arrayOfURLs enumerateObjectsUsingBlock:^(NSURL *logoImageURL, NSUInteger idx, BOOL * _Nonnull stop) { NSMutableArray *operationsArray = [[NSMutableArray alloc] init];
__weak __typeof(self) wself = self;
id <SDWebImageOperation> operation = [[SDWebImageManager sharedManager] loadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { for (NSURL *logoImageURL in arrayOfURLs) {
__strong typeof(wself) sself = wself; id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!sself) return; if (!wself) return;
dispatch_main_async_safe(^{ dispatch_main_sync_safe(^{
__strong UIImageView *sself = wself;
[sself stopAnimating]; [sself stopAnimating];
if (sself && image) { if (sself && image) {
NSMutableArray<UIImage *> *currentImages = [[sself animationImages] mutableCopy]; NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) { if (!currentImages) {
currentImages = [[NSMutableArray alloc] init]; currentImages = [[NSMutableArray alloc] init];
} }
[currentImages addObject:image];
// We know what index objects should be at when they are returned so
// we will put the object at the index, filling any empty indexes
// with the image that was returned too "early". These images will
// be overwritten. (does not require additional sorting datastructure)
while ([currentImages count] < idx) {
[currentImages addObject:image];
}
currentImages[idx] = image;
sself.animationImages = currentImages; sself.animationImages = currentImages;
[sself setNeedsLayout]; [sself setNeedsLayout];
...@@ -99,41 +131,151 @@ ...@@ -99,41 +131,151 @@
[sself startAnimating]; [sself startAnimating];
}); });
}]; }];
@synchronized (self) { [operationsArray addObject:operation];
[operationsArray addPointer:(__bridge void *)(operation)]; }
[self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
- (void)sd_cancelCurrentAnimationImagesLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"];
}
#pragma mark -
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)setShowActivityIndicatorView:(BOOL)show{
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)showActivityIndicatorView{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
- (void)addActivityIndicator {
if (!self.activityIndicator) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self getIndicatorStyle]];
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
dispatch_main_async_safe(^{
[self addSubview:self.activityIndicator];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
});
}
dispatch_main_async_safe(^{
[self.activityIndicator startAnimating];
});
}
- (void)removeActivityIndicator {
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
}
@end
@implementation UIImageView (WebCacheDeprecated)
- (NSURL *)imageURL {
return [self sd_imageURL];
}
- (void)setImageWithURL:(NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
} }
}]; }];
} }
static char animationLoadOperationKey; - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
// element is weak because operation instance is retained by SDWebImageManager's runningOperations property - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
// we should use lock to keep thread-safe because these method may not be acessed from main queue [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
- (NSPointerArray *)sd_animationOperationArray { if (completedBlock) {
@synchronized(self) { completedBlock(image, error, cacheType);
NSPointerArray *operationsArray = objc_getAssociatedObject(self, &animationLoadOperationKey);
if (operationsArray) {
return operationsArray;
} }
operationsArray = [NSPointerArray weakObjectsPointerArray]; }];
objc_setAssociatedObject(self, &animationLoadOperationKey, operationsArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operationsArray;
}
} }
- (void)sd_cancelCurrentAnimationImagesLoad { - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock {
NSPointerArray *operationsArray = [self sd_animationOperationArray]; [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (operationsArray) { if (completedBlock) {
@synchronized (self) { completedBlock(image, error, cacheType);
for (id operation in operationsArray) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
}
operationsArray.count = 0;
} }
} }];
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url andPlaceholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithPreviousCachedImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:completedBlock];
}
- (void)cancelCurrentArrayLoad {
[self sd_cancelCurrentAnimationImagesLoad];
}
- (void)cancelCurrentImageLoad {
[self sd_cancelCurrentImageLoad];
}
- (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
[self sd_setAnimationImagesWithURLs:arrayOfURLs];
} }
#endif
@end @end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "SDWebImageManager.h"
#import "SDWebImageTransition.h"
/**
A Dispatch group to maintain setImageBlock and completionBlock. This key should be used only internally and may be changed in the future. (dispatch_group_t)
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageGroupKey __deprecated_msg("Key Deprecated. Does nothing. This key should be used only internally");
/**
A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager)
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageExternalCustomManagerKey;
/**
The value specify that the image progress unit count cannot be determined because the progressBlock is not been called.
*/
FOUNDATION_EXPORT const int64_t SDWebImageProgressUnitCountUnknown; /* 1LL */
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
@interface UIView (WebCache)
/**
* Get the current image URL.
*
* @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly.
*/
- (nullable NSURL *)sd_imageURL;
/**
* The current image loading progress associated to the view. The unit count is the received size and excepted size of download.
* The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue).
* @note You can use Key-Value Observing on the progress, but you should take care that the change to progress is from a background queue during download(the same as progressBlock). If you want to using KVO and update the UI, make sure to dispatch on the main queue. And it's recommand to use some KVO libs like KVOController because it's more safe and easy to use.
* @note The getter will create a progress instance if the value is nil. You can also set a custom progress instance and let it been updated during image loading
* @note Note that because of the limitations of categories this property can get out of sync if you update the progress directly.
*/
@property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
* @param context A context with extra information to perform specify changes or processes.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context;
/**
* Cancel the current image load
*/
- (void)sd_cancelCurrentImageLoad;
#if SD_UIKIT || SD_MAC
#pragma mark - Image Transition
/**
The image transition when image load finished. See `SDWebImageTransition`.
If you specify nil, do not do transition. Defautls to nil.
*/
@property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition;
#if SD_UIKIT
#pragma mark - Activity indicator
/**
* Show activity UIActivityIndicatorView
*/
- (void)sd_setShowActivityIndicatorView:(BOOL)show;
/**
* set desired UIActivityIndicatorViewStyle
*
* @param style The style of the UIActivityIndicatorView
*/
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
- (BOOL)sd_showActivityIndicatorView;
- (void)sd_addActivityIndicator;
- (void)sd_removeActivityIndicator;
#endif
#endif
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIView+WebCache.h"
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
static char imageURLKey;
#if SD_UIKIT
static char TAG_ACTIVITY_INDICATOR;
static char TAG_ACTIVITY_STYLE;
static char TAG_ACTIVITY_SHOW;
#endif
@implementation UIView (WebCache)
- (nullable NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
}
- (NSProgress *)sd_imageProgress {
NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (!progress) {
progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
self.sd_imageProgress = progress;
}
return progress;
}
- (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
#if SD_UIKIT
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
#endif
// reset the progress
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) { return; }
#if SD_UIKIT
[sself sd_removeActivityIndicator];
#endif
// if the progress not been updated, mark it to complete state
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
#endif
callCompletedBlockClojure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
#if SD_UIKIT
[self sd_removeActivityIndicator];
#endif
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
}
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
#if SD_UIKIT || SD_MAC
[self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:0 imageURL:nil];
#else
// watchOS does not support view transition. Simplify the logic
if (setImageBlock) {
setImageBlock(image, imageData);
} else if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
[imageView setImage:image];
}
#endif
}
#if SD_UIKIT || SD_MAC
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
UIView *view = self;
SDSetImageBlock finalSetImageBlock;
if (setImageBlock) {
finalSetImageBlock = setImageBlock;
} else if ([view isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData) {
imageView.image = setImage;
};
}
#if SD_UIKIT
else if ([view isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData){
[button setImage:setImage forState:UIControlStateNormal];
};
}
#endif
if (transition) {
#if SD_UIKIT
[UIView transitionWithView:view duration:0 options:0 animations:^{
// 0 duration to let UIKit render placeholder and prepares block
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completion:^(BOOL finished) {
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
finalSetImageBlock(image, imageData);
}
if (transition.animations) {
transition.animations(view, image);
}
} completion:transition.completion];
}];
#elif SD_MAC
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
// 0 duration to let AppKit render placeholder and prepares block
prepareContext.duration = 0;
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completionHandler:^{
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
context.duration = transition.duration;
context.timingFunction = transition.timingFunction;
context.allowsImplicitAnimation = (transition.animationOptions & SDWebImageAnimationOptionAllowsImplicitAnimation);
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
finalSetImageBlock(image, imageData);
}
if (transition.animations) {
transition.animations(view, image);
}
} completionHandler:^{
if (transition.completion) {
transition.completion(YES);
}
}];
}];
#endif
} else {
if (finalSetImageBlock) {
finalSetImageBlock(image, imageData);
}
}
}
#endif
- (void)sd_setNeedsLayout {
#if SD_UIKIT
[self setNeedsLayout];
#elif SD_MAC
[self setNeedsLayout:YES];
#elif SD_WATCH
// Do nothing because WatchKit automatically layout the view after property change
#endif
}
#if SD_UIKIT || SD_MAC
#pragma mark - Image Transition
- (SDWebImageTransition *)sd_imageTransition {
return objc_getAssociatedObject(self, @selector(sd_imageTransition));
}
- (void)setSd_imageTransition:(SDWebImageTransition *)sd_imageTransition {
objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#if SD_UIKIT
#pragma mark - Activity indicator
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)sd_showActivityIndicatorView {
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)sd_getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
- (void)sd_addActivityIndicator {
dispatch_main_async_safe(^{
if (!self.activityIndicator) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.activityIndicator];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
}
[self.activityIndicator startAnimating];
});
}
- (void)sd_removeActivityIndicator {
dispatch_main_async_safe(^{
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
});
}
#endif
#endif
@end
...@@ -6,33 +6,31 @@ ...@@ -6,33 +6,31 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
#import "SDWebImageCompat.h" #import <UIKit/UIKit.h>
#import "SDWebImageOperation.h" #import "SDWebImageManager.h"
// These methods are used to support canceling for UIView image loading, it's designed to be used internal but not external.
// All the stored operations are weak, so it will be dalloced after image loading finished. If you need to store operations, use your own class to keep a strong reference for them.
@interface UIView (WebCacheOperation) @interface UIView (WebCacheOperation)
/** /**
* Set the image load operation (storage in a UIView based weak map table) * Set the image load operation (storage in a UIView based dictionary)
* *
* @param operation the operation * @param operation the operation
* @param key key for storing the operation * @param key key for storing the operation
*/ */
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key; - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
/** /**
* Cancel all operations for the current UIView and key * Cancel all operations for the current UIView and key
* *
* @param key key for identifying the operations * @param key key for identifying the operations
*/ */
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key; - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
/** /**
* Just remove the operations corresponding to the current UIView and key without cancelling them * Just remove the operations corresponding to the current UIView and key without cancelling them
* *
* @param key key for identifying the operations * @param key key for identifying the operations
*/ */
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key; - (void)sd_removeImageLoadOperationWithKey:(NSString *)key;
@end @end
...@@ -11,63 +11,45 @@ ...@@ -11,63 +11,45 @@
static char loadOperationKey; static char loadOperationKey;
// key is copy, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
@implementation UIView (WebCacheOperation) @implementation UIView (WebCacheOperation)
- (SDOperationsDictionary *)sd_operationDictionary { - (NSMutableDictionary *)operationDictionary {
@synchronized(self) { NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); if (operations) {
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations; return operations;
} }
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
} }
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key { - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
if (key) { [self sd_cancelImageLoadOperationWithKey:key];
[self sd_cancelImageLoadOperationWithKey:key]; NSMutableDictionary *operationDictionary = [self operationDictionary];
if (operation) { [operationDictionary setObject:operation forKey:key];
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
} }
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key { - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
if (key) { // Cancel in progress downloader from queue
// Cancel in progress downloader from queue NSMutableDictionary *operationDictionary = [self operationDictionary];
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; id operations = [operationDictionary objectForKey:key];
id<SDWebImageOperation> operation; if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
@synchronized (self) { for (id <SDWebImageOperation> operation in operations) {
operation = [operationDictionary objectForKey:key]; if (operation) {
} [operation cancel];
if (operation) { }
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
} }
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
} }
[operationDictionary removeObjectForKey:key];
} }
} }
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key { - (void)sd_removeImageLoadOperationWithKey:(NSString *)key {
if (key) { NSMutableDictionary *operationDictionary = [self operationDictionary];
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; [operationDictionary removeObjectForKey:key];
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
} }
@end @end
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.7.0</string> <string>1.4.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>BNDL</string> <string>BNDL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.5.3</string> <string>1.4.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -2391,7 +2391,7 @@ regulations. ...@@ -2391,7 +2391,7 @@ regulations.
## SDWebImage ## SDWebImage
Copyright (c) 2009-2017 Olivier Poitrey rs@dailymotion.com Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
...@@ -2546,7 +2546,7 @@ regulations. ...@@ -2546,7 +2546,7 @@ regulations.
</dict> </dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2009-2017 Olivier Poitrey rs@dailymotion.com <string>Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>4.4.2</string> <string>3.8.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -10,32 +10,20 @@ ...@@ -10,32 +10,20 @@
#endif #endif
#endif #endif
#import "NSButton+WebCache.h"
#import "NSData+ImageContentType.h" #import "NSData+ImageContentType.h"
#import "NSImage+WebCache.h"
#import "SDAnimatedImageRep.h"
#import "SDImageCache.h" #import "SDImageCache.h"
#import "SDImageCacheConfig.h"
#import "SDWebImageCoder.h"
#import "SDWebImageCoderHelper.h"
#import "SDWebImageCodersManager.h"
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDWebImageDecoder.h"
#import "SDWebImageDownloader.h" #import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h" #import "SDWebImageDownloaderOperation.h"
#import "SDWebImageFrame.h"
#import "SDWebImageGIFCoder.h"
#import "SDWebImageImageIOCoder.h"
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
#import "SDWebImageOperation.h" #import "SDWebImageOperation.h"
#import "SDWebImagePrefetcher.h" #import "SDWebImagePrefetcher.h"
#import "SDWebImageTransition.h"
#import "UIButton+WebCache.h" #import "UIButton+WebCache.h"
#import "UIImage+ForceDecode.h"
#import "UIImage+GIF.h" #import "UIImage+GIF.h"
#import "UIImage+MultiFormat.h" #import "UIImage+MultiFormat.h"
#import "UIImageView+HighlightedWebCache.h" #import "UIImageView+HighlightedWebCache.h"
#import "UIImageView+WebCache.h" #import "UIImageView+WebCache.h"
#import "UIView+WebCache.h"
#import "UIView+WebCacheOperation.h" #import "UIView+WebCacheOperation.h"
FOUNDATION_EXPORT double SDWebImageVersionNumber; FOUNDATION_EXPORT double SDWebImageVersionNumber;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment