Explorar o código

新增渐进式JPEG加载演示页面, 完成ImageView的渐进式JPEG加载扩展

lixiang1994 %!s(int64=6) %!d(string=hai) anos
pai
achega
a04faeddd6

+ 70 - 2
Demo/Demo/Kingfisher-Demo/Base.lproj/Main.storyboard

@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="peg-r0-mlo">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="peg-r0-mlo">
     <device id="retina5_5" orientation="portrait">
         <adaptation id="fullscreen"/>
     </device>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>
@@ -326,6 +326,30 @@
                                             <segue destination="8m3-Cr-mNn" kind="show" id="ArT-mj-SaD"/>
                                         </connections>
                                     </tableViewCell>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="cln-Yy-v33">
+                                        <rect key="frame" x="0.0" y="352" width="414" height="44"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="cln-Yy-v33" id="uVh-9Y-8Dr">
+                                            <rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Progressive JPEG" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OU9-Px-L6V">
+                                                    <rect key="frame" x="20" y="11.333333333333336" width="135.33333333333334" height="21"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstItem="OU9-Px-L6V" firstAttribute="leading" secondItem="uVh-9Y-8Dr" secondAttribute="leading" constant="20" symbolic="YES" id="Cne-mx-kSy"/>
+                                                <constraint firstItem="OU9-Px-L6V" firstAttribute="centerY" secondItem="uVh-9Y-8Dr" secondAttribute="centerY" id="eIS-yb-Vkf"/>
+                                                <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="OU9-Px-L6V" secondAttribute="trailing" constant="20" symbolic="YES" id="xL2-ku-p7g"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <connections>
+                                            <segue destination="P3U-9B-Crn" kind="show" id="KUJ-hl-oad"/>
+                                        </connections>
+                                    </tableViewCell>
                                 </cells>
                             </tableViewSection>
                         </sections>
@@ -671,6 +695,50 @@
             </objects>
             <point key="canvasLocation" x="1851" y="1062"/>
         </scene>
+        <!--ProgressiveJPEG View Controller-->
+        <scene sceneID="aU5-xT-lmw">
+            <objects>
+                <viewController id="P3U-9B-Crn" customClass="ProgressiveJPEGViewController" customModule="Kingfisher_Demo" customModuleProvider="target" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="yCs-FZ-4DV"/>
+                        <viewControllerLayoutGuide type="bottom" id="cxP-E0-3ef"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="cGe-Lq-jhl">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="akD-zs-eTn">
+                                <rect key="frame" x="89" y="74" width="236" height="236"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="236" id="HYO-L6-Htj"/>
+                                    <constraint firstAttribute="height" constant="236" id="Lyz-IW-83o"/>
+                                </constraints>
+                            </imageView>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="izF-cD-0cm">
+                                <rect key="frame" x="0.0" y="340" width="414" height="0.0"/>
+                                <fontDescription key="fontDescription" name="DINAlternate-Bold" family="DIN Alternate" pointSize="17"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                        </subviews>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstItem="akD-zs-eTn" firstAttribute="centerX" secondItem="cGe-Lq-jhl" secondAttribute="centerX" id="5kh-xV-bka"/>
+                            <constraint firstItem="izF-cD-0cm" firstAttribute="width" secondItem="cGe-Lq-jhl" secondAttribute="width" id="Aut-Lw-Huc"/>
+                            <constraint firstItem="akD-zs-eTn" firstAttribute="top" secondItem="yCs-FZ-4DV" secondAttribute="bottom" constant="10" id="HtJ-QY-RlR"/>
+                            <constraint firstItem="izF-cD-0cm" firstAttribute="centerX" secondItem="cGe-Lq-jhl" secondAttribute="centerX" id="i2T-YC-hzq"/>
+                            <constraint firstItem="izF-cD-0cm" firstAttribute="top" secondItem="akD-zs-eTn" secondAttribute="bottom" constant="30" id="oD0-d7-geg"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="imageView" destination="akD-zs-eTn" id="w4n-Wq-2gb"/>
+                        <outlet property="progressLabel" destination="izF-cD-0cm" id="Pg5-6G-GQa"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="TkP-Lx-DcG" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="3441" y="1074"/>
+        </scene>
         <!--Image Data Provider Collection View Controller-->
         <scene sceneID="LEm-4i-lfK">
             <objects>

+ 5 - 0
Demo/Demo/Kingfisher-Demo/Info.plist

@@ -22,6 +22,11 @@
 	<string>1244</string>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
 	<key>UILaunchStoryboardName</key>
 	<string>LaunchScreen</string>
 	<key>UIMainStoryboardFile</key>

+ 55 - 0
Demo/Demo/Kingfisher-Demo/ViewControllers/ProgressiveJPEGViewController.swift

@@ -0,0 +1,55 @@
+//
+//  ProgressiveJPEGViewController.swift
+//  Kingfisher
+//
+//  Created by lixiang on 2019/5/12.
+//
+//  Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+import UIKit
+
+class ProgressiveJPEGViewController: UIViewController {
+
+    @IBOutlet weak var imageView: UIImageView!
+    @IBOutlet weak var progressLabel: UILabel!
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Loading"
+        setupOperationNavigationBar()
+        
+        imageView.kf.setImage(
+//            with: ImageLoader.sampleImageURLs[indexPath.row],
+//            with: URL(string: "http://calm.chongdingdahui.com/812075.jpg"),
+            with: URL(string: "https://demo-resources.oss-cn-beijing.aliyuncs.com/progressive.jpeg"),
+            placeholder: nil,
+            options: [.loadDiskFileSynchronously, .progressiveJPEG],
+            progressBlock: { receivedSize, totalSize in
+                print("\(receivedSize)/\(totalSize)")
+                self.progressLabel.text = "\(receivedSize) / \(totalSize)"
+            },
+            completionHandler: { result in
+                print(result)
+                print("Finished")
+            }
+        )
+    }
+}

+ 19 - 15
Demo/Kingfisher-Demo.xcodeproj/project.pbxproj

@@ -21,6 +21,7 @@
 		4BCCF33F1D5B02F8003387C2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BCCF3381D5B02F8003387C2 /* Main.storyboard */; };
 		4BCCF3401D5B02F8003387C2 /* Cell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BCCF33A1D5B02F8003387C2 /* Cell.xib */; };
 		4BCCF3421D5B02F8003387C2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCCF33C1D5B02F8003387C2 /* ViewController.swift */; };
+		C959EEE622874DC600467A10 /* ProgressiveJPEGViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C959EEE522874DC600467A10 /* ProgressiveJPEGViewController.swift */; };
 		D10AC99821A300C9005F057C /* ProcessorCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D10AC99721A300C9005F057C /* ProcessorCollectionViewController.swift */; };
 		D12E0C951C47F91800AC98AD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C8C1C47F91800AC98AD /* AppDelegate.swift */; };
 		D12E0C961C47F91800AC98AD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D12E0C8D1C47F91800AC98AD /* LaunchScreen.xib */; };
@@ -147,6 +148,7 @@
 		4BCCF33A1D5B02F8003387C2 /* Cell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Cell.xib; sourceTree = "<group>"; };
 		4BCCF33B1D5B02F8003387C2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		4BCCF33C1D5B02F8003387C2 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
+		C959EEE522874DC600467A10 /* ProgressiveJPEGViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressiveJPEGViewController.swift; sourceTree = "<group>"; };
 		D10AC99721A300C9005F057C /* ProcessorCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessorCollectionViewController.swift; sourceTree = "<group>"; };
 		D12E0C8C1C47F91800AC98AD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		D12E0C8E1C47F91800AC98AD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
@@ -331,6 +333,7 @@
 				D1F06F3621AAEACF000B1C38 /* GIFViewController.swift */,
 				D1F06F3821AAF1EE000B1C38 /* IndicatorCollectionViewController.swift */,
 				D1E4CF5321BACBA6004D029D /* ImageDataProviderCollectionViewController.swift */,
+				C959EEE522874DC600467A10 /* ProgressiveJPEGViewController.swift */,
 			);
 			path = ViewControllers;
 			sourceTree = "<group>";
@@ -471,30 +474,30 @@
 				TargetAttributes = {
 					4B2944541C3D03880088C3E7 = {
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = 683UGRW72Z;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 					};
 					D13F49C11BEDA53F00CE335D = {
 						CreatedOnToolsVersion = 7.1;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = 683UGRW72Z;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 					};
 					D1679A381C4E78B20020FD12 = {
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = 683UGRW72Z;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 					};
 					D1679A441C4E78B20020FD12 = {
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = 683UGRW72Z;
 						LastSwiftMigration = 0920;
 					};
 					D1ED2D0A1AD2CFA600CFC3EB = {
 						CreatedOnToolsVersion = 6.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = 683UGRW72Z;
 						LastSwiftMigration = 0900;
 					};
 				};
@@ -608,6 +611,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				C959EEE622874DC600467A10 /* ProgressiveJPEGViewController.swift in Sources */,
 				D1CE1BD321A1B45A00419000 /* ImageLoader.swift in Sources */,
 				D12E0C9B1C47F91800AC98AD /* NormalLoadingViewController.swift in Sources */,
 				D1CE1BD021A1AFA300419000 /* TransitionViewController.swift in Sources */,
@@ -692,7 +696,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@@ -711,7 +715,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@@ -731,7 +735,7 @@
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -751,7 +755,7 @@
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -769,7 +773,7 @@
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
@@ -787,7 +791,7 @@
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
@@ -809,7 +813,7 @@
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
@@ -831,7 +835,7 @@
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				GCC_NO_COMMON_BLOCKS = YES;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
@@ -962,7 +966,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";
@@ -975,7 +979,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = 683UGRW72Z;
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";

+ 8 - 0
Demo/Kingfisher-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 16 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -56,6 +56,13 @@
 		4BD821692189FD330084CC21 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD821662189FD330084CC21 /* SessionDataTask.swift */; };
 		4BD8216A2189FD330084CC21 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD821662189FD330084CC21 /* SessionDataTask.swift */; };
 		AE1D6776DC6183B84B561961 /* libPods-KingfisherTests-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 124FABC032484C46AC221D0C /* libPods-KingfisherTests-tvOS.a */; };
+		C9286407228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C9286408228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C9286409228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C928640A228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C928640B228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C928640C228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
+		C928640D228584EB00257182 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9286406228584EB00257182 /* ImageProgressive.swift */; };
 		D10EC2361C3D632300A4211C /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B2944481C3D01B20088C3E7 /* Kingfisher.framework */; };
 		D114F36E215D2D0B00A01349 /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6BC215D2BB50013BA68 /* String+MD5.swift */; };
 		D12AB6C0215D2BB50013BA68 /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB69D215D2BB50013BA68 /* RequestModifier.swift */; };
@@ -298,6 +305,7 @@
 		4BD821662189FD330084CC21 /* SessionDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDataTask.swift; sourceTree = "<group>"; };
 		6CD5C0134AA4B1C0892E7319 /* Pods-KingfisherTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests/Pods-KingfisherTests.release.xcconfig"; sourceTree = "<group>"; };
 		7204D40BEFEA059FA25864C4 /* Pods-KingfisherTests-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests-macOS/Pods-KingfisherTests-macOS.debug.xcconfig"; sourceTree = "<group>"; };
+		C9286406228584EB00257182 /* ImageProgressive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProgressive.swift; sourceTree = "<group>"; };
 		CCDD057F8DA8D24EE701CF98 /* libPods-KingfisherTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KingfisherTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D10EC2311C3D632300A4211C /* KingfisherTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "KingfisherTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D12AB69D215D2BB50013BA68 /* RequestModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestModifier.swift; sourceTree = "<group>"; };
@@ -507,6 +515,7 @@
 				D1A37BE2215D359F009B39B7 /* ImageFormat.swift */,
 				D12AB6A4215D2BB50013BA68 /* ImageTransition.swift */,
 				D12AB6A5215D2BB50013BA68 /* ImageProcessor.swift */,
+				C9286406228584EB00257182 /* ImageProgressive.swift */,
 				D12AB6A6215D2BB50013BA68 /* Filter.swift */,
 				D12AB6A7215D2BB50013BA68 /* Placeholder.swift */,
 				D12AB6A8215D2BB50013BA68 /* GIFAnimatedImage.swift */,
@@ -1095,6 +1104,7 @@
 				4B46CC61217449C600D90C4A /* MemoryStorage.swift in Sources */,
 				D1A37BD5215D2DBA009B39B7 /* RequestModifier.swift in Sources */,
 				D1A37BEA215D365A009B39B7 /* ExtensionHelpers.swift in Sources */,
+				C9286409228584EB00257182 /* ImageProgressive.swift in Sources */,
 				4BD821642189FC0C0084CC21 /* SessionDelegate.swift in Sources */,
 				D1A37BD6215D2DBA009B39B7 /* Resource.swift in Sources */,
 				D1A37BD7215D2DBA009B39B7 /* ImageDownloader.swift in Sources */,
@@ -1128,6 +1138,7 @@
 				D1A1CCA021A0F98600263AD8 /* ImageDataProviderTests.swift in Sources */,
 				4B8351C9217066580081EED8 /* StubHelpers.swift in Sources */,
 				D1DC4B421D60996D00DFDFAA /* StringExtensionTests.swift in Sources */,
+				C928640C228584EB00257182 /* ImageProgressive.swift in Sources */,
 				D1BFED96222ACC6B009330C8 /* ImageProcessorTests.swift in Sources */,
 				D12E0C741C47F6FE00AC98AD /* UIButtonExtensionTests.swift in Sources */,
 			);
@@ -1153,6 +1164,7 @@
 				D1A1CCA121A0F98600263AD8 /* ImageDataProviderTests.swift in Sources */,
 				4B8351CA217066580081EED8 /* StubHelpers.swift in Sources */,
 				D1DC4B431D60996D00DFDFAA /* StringExtensionTests.swift in Sources */,
+				C928640D228584EB00257182 /* ImageProgressive.swift in Sources */,
 				D1BFED97222ACC6B009330C8 /* ImageProcessorTests.swift in Sources */,
 				D12E0C871C47F7AF00AC98AD /* KingfisherOptionsInfoTests.swift in Sources */,
 			);
@@ -1173,6 +1185,7 @@
 				D1839846216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB6D9215D2BB50013BA68 /* ImageTransition.swift in Sources */,
 				D1A37BE9215D365A009B39B7 /* ExtensionHelpers.swift in Sources */,
+				C9286408228584EB00257182 /* ImageProgressive.swift in Sources */,
 				D12AB6DD215D2BB50013BA68 /* ImageProcessor.swift in Sources */,
 				D12AB6D5215D2BB50013BA68 /* Image.swift in Sources */,
 				D12AB729215D2BB50013BA68 /* String+MD5.swift in Sources */,
@@ -1234,6 +1247,7 @@
 				D1839848216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB70F215D2BB50013BA68 /* KingfisherManager.swift in Sources */,
 				D1A37BF0215D375F009B39B7 /* Deprecated.swift in Sources */,
+				C928640A228584EB00257182 /* ImageProgressive.swift in Sources */,
 				4B8351CF217084660081EED8 /* Runtime.swift in Sources */,
 				D13646772165A1A100A33652 /* Result.swift in Sources */,
 				D12AB6C3215D2BB50013BA68 /* RequestModifier.swift in Sources */,
@@ -1270,6 +1284,7 @@
 				D1839845216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB6D8215D2BB50013BA68 /* ImageTransition.swift in Sources */,
 				D1A37BE8215D365A009B39B7 /* ExtensionHelpers.swift in Sources */,
+				C9286407228584EB00257182 /* ImageProgressive.swift in Sources */,
 				D12AB6DC215D2BB50013BA68 /* ImageProcessor.swift in Sources */,
 				D12AB6D4215D2BB50013BA68 /* Image.swift in Sources */,
 				D12AB728215D2BB50013BA68 /* String+MD5.swift in Sources */,
@@ -1326,6 +1341,7 @@
 				D1A1CC9F21A0F98600263AD8 /* ImageDataProviderTests.swift in Sources */,
 				4B8351C8217066580081EED8 /* StubHelpers.swift in Sources */,
 				D1DC4B411D60996D00DFDFAA /* StringExtensionTests.swift in Sources */,
+				C928640B228584EB00257182 /* ImageProgressive.swift in Sources */,
 				D1BFED95222ACC6B009330C8 /* ImageProcessorTests.swift in Sources */,
 				D12E0C501C47F23500AC98AD /* ImageCacheTests.swift in Sources */,
 			);

+ 23 - 3
Sources/Extensions/ImageView+Kingfisher.swift

@@ -104,10 +104,30 @@ extension KingfisherWrapper where Base: ImageView {
         if base.shouldPreloadAllAnimation() {
             options.preloadAllAnimationData = true
         }
-
+        
+        let progressive = ImageProgressive(options)
+        
+        let dataUpdate = { (data: Data) in
+            guard
+                let data = progressive.scanning(data),
+                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks else {
+                return
+            }
+            progressive.decode(data, with: callbacks) { (image) in
+                guard mutatingSelf.imageTask != nil else { return }
+                
+                self.base.image = image
+            }
+        }
+        
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
+            receivedBlock: { latest, received in
+                guard issuedIdentifier == self.taskIdentifier else { return }
+                
+                dataUpdate(received)
+            },
             progressBlock: { receivedSize, totalSize in
                 guard issuedIdentifier == self.taskIdentifier else { return }
                 if let progressBlock = progressBlock {
@@ -151,8 +171,8 @@ extension KingfisherWrapper where Base: ImageView {
                         completionHandler?(result)
                     }
                 }
-        })
-
+            }
+        )
         mutatingSelf.imageTask = task
         return task
     }

+ 5 - 1
Sources/General/KingfisherOptionsInfo.swift

@@ -213,6 +213,8 @@ public enum KingfisherOptionsInfoItem {
     /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of
     /// blocking the UI, especially if the processor needs a lot of time to run).
     case processingQueue(CallbackQueue)
+    
+    case progressiveJPEG
 }
 
 // Improve performance by parsing the input `KingfisherOptionsInfo` (self) first.
@@ -251,7 +253,8 @@ public struct KingfisherParsedOptionsInfo {
     public var memoryCacheExpiration: StorageExpiration? = nil
     public var diskCacheExpiration: StorageExpiration? = nil
     public var processingQueue: CallbackQueue? = nil
-
+    public var progressiveJPEG = false
+    
     public init(_ info: KingfisherOptionsInfo?) {
         guard let info = info else { return }
         for option in info {
@@ -286,6 +289,7 @@ public struct KingfisherParsedOptionsInfo {
             case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
             case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
             case .processingQueue(let queue): processingQueue = queue
+            case .progressiveJPEG: progressiveJPEG = true
             }
         }
 

+ 93 - 0
Sources/Image/ImageProgressive.swift

@@ -0,0 +1,93 @@
+//
+//  ImageProgressive.swift
+//  Kingfisher
+//
+//  Created by lixiang on 2019/5/10.
+//
+//  Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+import UIKit
+
+final class ImageProgressive {
+    
+    private let options: KingfisherParsedOptionsInfo
+    private(set) var scannedCount: Int = 0
+    private var scannedIndex = -1
+    private var lastSOSIndex = 0
+    
+    init(_ options: KingfisherParsedOptionsInfo) {
+        self.options = options
+    }
+    
+    func scanning(_ data: Data) -> Data? {
+        guard options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
+            return nil
+        }
+        guard (scannedIndex + 1) < data.count else {
+            return nil
+        }
+        
+        var index = scannedIndex + 1
+        var count = scannedCount
+        
+        while index < (data.count - 1) {
+            scannedIndex = index
+            // 0xFF, 0xDA - Start Of Scan
+            let SOS = ImageFormat.JPEGMarker.SOS.bytes
+            if data[index] == SOS[0], data[index + 1] == SOS[1] {
+                lastSOSIndex = index
+                count += 1
+            }
+            index += 1
+        }
+        
+        // Found more scans this the previous time
+        guard count > scannedCount else { return nil }
+        scannedCount = count
+        
+        // `> 1` checks that we've received a first scan (SOS) and then received
+        // and also received a second scan (SOS). This way we know that we have
+        // at least one full scan available.
+        guard count > 1 && lastSOSIndex > 0 else { return nil }
+        return data[0 ..< lastSOSIndex]
+    }
+    
+    func decode(_ data: Data, with callbacks: [SessionDataTask.TaskCallback], completion: @escaping (Image) -> Void) {
+        let processor = ImageDataProcessor(
+            data: data[0 ..< lastSOSIndex],
+            callbacks: callbacks,
+            processingQueue: options.processingQueue
+        )
+        processor.onImageProcessed.delegate(on: self) { (self, result) in
+            guard let image = try? result.0.get() else { return }
+            
+            // Blur partial images.
+//            if self.scannedCount < 5 {
+//                // Progressively reduce blur as we load more scans.
+//                let radius = max(2, 14 - self.scannedCount * 4)
+//                image = image.kf.blurred(withRadius: CGFloat(radius))
+//            }
+
+            CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
+        }
+        processor.process()
+    }
+}

+ 4 - 2
Sources/Networking/ImageDownloader.swift

@@ -290,13 +290,15 @@ open class ImageDownloader {
                         self,
                         didFinishDownloadingImageForURL: url,
                         with: value.1,
-                        error: nil)
+                        error: nil
+                    )
                 } catch {
                     self.delegate?.imageDownloader(
                         self,
                         didFinishDownloadingImageForURL: url,
                         with: nil,
-                        error: error)
+                        error: error
+                    )
                 }
 
                 switch result {