├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── workflows │ ├── build.yaml │ └── test.yaml ├── .gitignore ├── .ruby-version ├── .spi.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Demo ├── Demo │ ├── Kingfisher-Demo │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ ├── Extensions │ │ │ └── UIViewController+KingfisherOperation.swift │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── Resources │ │ │ ├── ImageLoader.swift │ │ │ └── loader.gif │ │ ├── SwiftUIViews │ │ │ ├── AnimatedImageDemo.swift │ │ │ ├── GeometryReaderDemo.swift │ │ │ ├── GridDemo.swift │ │ │ ├── LazyVStackDemo.swift │ │ │ ├── ListDemo.swift │ │ │ ├── MainView.swift │ │ │ ├── ProgressiveJPEGDemo.swift │ │ │ ├── Regression │ │ │ │ ├── Issue1998View.swift │ │ │ │ ├── Issue2035View.swift │ │ │ │ ├── Issue2295View.swift │ │ │ │ └── Issue2352View.swift │ │ │ ├── SingleViewDemo.swift │ │ │ ├── SizingAnimationDemo.swift │ │ │ └── TransitionViewDemo.swift │ │ └── ViewControllers │ │ │ ├── AVAssetImageGeneratorViewController.swift │ │ │ ├── AutoSizingTableViewController.swift │ │ │ ├── DetailImageViewController.swift │ │ │ ├── GIFHeavyViewController.swift │ │ │ ├── GIFViewController.swift │ │ │ ├── HighResolutionCollectionViewController.swift │ │ │ ├── ImageCollectionViewCell.swift │ │ │ ├── ImageDataProviderCollectionViewController.swift │ │ │ ├── IndicatorCollectionViewController.swift │ │ │ ├── InfinityCollectionViewController.swift │ │ │ ├── LivePhotoViewController.swift │ │ │ ├── MainViewController.swift │ │ │ ├── NormalLoadingViewController.swift │ │ │ ├── OrientationImagesViewController.swift │ │ │ ├── PHPickerResultViewController.swift │ │ │ ├── ProcessorCollectionViewController.swift │ │ │ ├── ProgressiveJPEGViewController.swift │ │ │ ├── SwiftUIViewController.swift │ │ │ ├── TextAttachmentViewController.swift │ │ │ └── TransitionViewController.swift │ ├── Kingfisher-macOS-Demo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ ├── Cell.xib │ │ ├── GIFHeavyViewController.swift │ │ ├── Info.plist │ │ ├── SwiftUIViewController.swift │ │ └── ViewController.swift │ ├── Kingfisher-tvOS-Demo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ │ ├── App Icon - Large.imagestack │ │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── App Icon - Small.imagestack │ │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Top Shelf Image.imageset │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── LaunchImage.launchimage │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ └── Info.plist │ ├── Kingfisher-watchOS-Demo Extension │ │ ├── Assets.xcassets │ │ │ └── README__ignoredByTemplate__ │ │ ├── ExtensionDelegate.swift │ │ ├── Info.plist │ │ └── InterfaceController.swift │ └── Kingfisher-watchOS-Demo │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Interface.storyboard │ │ └── Info.plist ├── Kingfisher-Demo.entitlements └── Kingfisher-Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── Kingfisher-Demo.xcscheme ├── Gemfile ├── Gemfile.lock ├── Kingfisher.podspec ├── Kingfisher.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ ├── xcbaselines │ └── D1ED2D3E1AD2D09F00CFC3EB.xcbaseline │ │ ├── 74237B0B-7981-4A24-B6C4-95F4A5E7727F.plist │ │ └── Info.plist │ └── xcschemes │ └── Kingfisher.xcscheme ├── Kingfisher.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDETemplateMacros.plist │ ├── IDEWorkspaceChecks.plist │ ├── Kingfisher.xcscmblueprint │ └── WorkspaceSettings.xcsettings ├── LICENSE ├── Package.swift ├── Package@swift-5.9.swift ├── README.md ├── Sources ├── Cache │ ├── CacheSerializer.swift │ ├── DiskStorage.swift │ ├── FormatIndicatedCacheSerializer.swift │ ├── ImageCache.swift │ ├── MemoryStorage.swift │ └── Storage.swift ├── Documentation.docc │ ├── CHANGELOG.md │ ├── CommonTasks │ │ ├── CommonTasks.md │ │ ├── CommonTasks_Cache.md │ │ ├── CommonTasks_Downloader.md │ │ ├── CommonTasks_Processor.md │ │ └── CommonTasks_Serializer.md │ ├── Documentation.md │ ├── GettingStarted.md │ ├── MigrationGuide.md │ ├── MigrationGuide │ │ ├── Migration-To-6.md │ │ ├── Migration-To-7.md │ │ └── Migration-To-8.md │ ├── Resources │ │ ├── code-files │ │ │ ├── 01-SampleCell-1.swift │ │ │ ├── 01-SampleCell-2.swift │ │ │ ├── 01-SampleCell-3.swift │ │ │ ├── 01-ViewController-1.swift │ │ │ ├── 01-ViewController-10.swift │ │ │ ├── 01-ViewController-11.swift │ │ │ ├── 01-ViewController-12.swift │ │ │ ├── 01-ViewController-13.swift │ │ │ ├── 01-ViewController-2.swift │ │ │ ├── 01-ViewController-3.swift │ │ │ ├── 01-ViewController-4.swift │ │ │ ├── 01-ViewController-5.swift │ │ │ ├── 01-ViewController-6-0.swift │ │ │ ├── 01-ViewController-6.swift │ │ │ ├── 01-ViewController-7.swift │ │ │ ├── 01-ViewController-8.swift │ │ │ ├── 01-ViewController-9.swift │ │ │ ├── 02-ContentView-1.swift │ │ │ ├── 02-ContentView-10.swift │ │ │ ├── 02-ContentView-11.swift │ │ │ ├── 02-ContentView-2.swift │ │ │ ├── 02-ContentView-3.swift │ │ │ ├── 02-ContentView-4.swift │ │ │ ├── 02-ContentView-5.swift │ │ │ ├── 02-ContentView-6.swift │ │ │ ├── 02-ContentView-7.swift │ │ │ ├── 02-ContentView-8.swift │ │ │ └── 02-ContentView-9.swift │ │ ├── doc-art │ │ │ ├── common-tasks-card@2x.png │ │ │ ├── getting-started-card@2x.png │ │ │ ├── imagedataprovider-sample@2x.png │ │ │ └── logo@2x.png │ │ └── tutorial-art │ │ │ ├── add-dependency.png │ │ │ ├── add-library-swiftui.png │ │ │ ├── add-library.png │ │ │ ├── add-to-project.png │ │ │ ├── create-project-swiftui.png │ │ │ ├── create-project.png │ │ │ ├── preview-1-swiftui.png │ │ │ ├── preview-1.png │ │ │ ├── preview-2-swiftui.png │ │ │ ├── preview-2.png │ │ │ ├── preview-3-swiftui.png │ │ │ ├── preview-3.png │ │ │ ├── preview-4-swiftui.png │ │ │ ├── preview-4.png │ │ │ ├── preview-5-swiftui.png │ │ │ └── preview-5.png │ ├── Topics │ │ ├── Topic_ImageDataProvider.md │ │ ├── Topic_Indicator.md │ │ ├── Topic_LivePhoto.md │ │ ├── Topic_LowDataMode.md │ │ ├── Topic_PerformanceTips.md │ │ ├── Topic_Prefetch.md │ │ └── Topic_Retry.md │ └── Tutorials │ │ ├── GettingStartedSwiftUI.tutorial │ │ ├── GettingStartedUIKit.tutorial │ │ └── Tutorials.tutorial ├── Extensions │ ├── CPListItem+Kingfisher.swift │ ├── HasImageComponent+Kingfisher.swift │ ├── ImageView+Kingfisher.swift │ ├── NSButton+Kingfisher.swift │ ├── NSTextAttachment+Kingfisher.swift │ ├── PHLivePhotoView+Kingfisher.swift │ └── UIButton+Kingfisher.swift ├── General │ ├── ImageSource │ │ ├── AVAssetImageDataProvider.swift │ │ ├── ImageDataProvider.swift │ │ ├── LivePhotoSource.swift │ │ ├── PHPickerResultImageDataProvider.swift │ │ ├── Resource.swift │ │ └── Source.swift │ ├── KF.swift │ ├── KFOptionsSetter.swift │ ├── Kingfisher.swift │ ├── KingfisherError.swift │ ├── KingfisherManager+LivePhoto.swift │ ├── KingfisherManager.swift │ └── KingfisherOptionsInfo.swift ├── Image │ ├── Filter.swift │ ├── GIFAnimatedImage.swift │ ├── GraphicsContext.swift │ ├── Image.swift │ ├── ImageDrawing.swift │ ├── ImageFormat.swift │ ├── ImageProcessor.swift │ ├── ImageProgressive.swift │ ├── ImageTransition.swift │ └── Placeholder.swift ├── Info.plist ├── Networking │ ├── AuthenticationChallengeResponsable.swift │ ├── ImageDataProcessor.swift │ ├── ImageDownloader+LivePhoto.swift │ ├── ImageDownloader.swift │ ├── ImageDownloaderDelegate.swift │ ├── ImageModifier.swift │ ├── ImagePrefetcher.swift │ ├── RedirectHandler.swift │ ├── RequestModifier.swift │ ├── RetryStrategy.swift │ ├── SessionDataTask.swift │ └── SessionDelegate.swift ├── PrivacyInfo.xcprivacy ├── SwiftUI │ ├── ImageBinder.swift │ ├── ImageContext.swift │ ├── KFAnimatedImage.swift │ ├── KFImage.swift │ ├── KFImageOptions.swift │ ├── KFImageProtocol.swift │ └── KFImageRenderer.swift ├── Utility │ ├── Box.swift │ ├── CallbackQueue.swift │ ├── Delegate.swift │ ├── DisplayLink.swift │ ├── ExtensionHelpers.swift │ ├── Result.swift │ ├── Runtime.swift │ ├── SizeExtensions.swift │ └── String+SHA256.swift └── Views │ ├── AnimatedImageView.swift │ └── Indicator.swift ├── Tests ├── Dependency │ └── Nocilla │ │ ├── LICENSE │ │ ├── Nocilla │ │ ├── Categories │ │ │ ├── NSData+Nocilla.h │ │ │ ├── NSData+Nocilla.m │ │ │ ├── NSString+Nocilla.h │ │ │ └── NSString+Nocilla.m │ │ ├── DSL │ │ │ ├── LSHTTPRequestDSLRepresentation.h │ │ │ ├── LSHTTPRequestDSLRepresentation.m │ │ │ ├── LSStubRequestDSL.h │ │ │ ├── LSStubRequestDSL.m │ │ │ ├── LSStubResponseDSL.h │ │ │ └── LSStubResponseDSL.m │ │ ├── Diff │ │ │ ├── LSHTTPRequestDiff.h │ │ │ └── LSHTTPRequestDiff.m │ │ ├── Hooks │ │ │ ├── ASIHTTPRequest │ │ │ │ ├── ASIHTTPRequestStub.h │ │ │ │ ├── ASIHTTPRequestStub.m │ │ │ │ ├── LSASIHTTPRequestAdapter.h │ │ │ │ ├── LSASIHTTPRequestAdapter.m │ │ │ │ ├── LSASIHTTPRequestHook.h │ │ │ │ └── LSASIHTTPRequestHook.m │ │ │ ├── LSHTTPClientHook.h │ │ │ ├── LSHTTPClientHook.m │ │ │ ├── NSURLRequest │ │ │ │ ├── LSHTTPStubURLProtocol.h │ │ │ │ ├── LSHTTPStubURLProtocol.m │ │ │ │ ├── LSNSURLHook.h │ │ │ │ ├── LSNSURLHook.m │ │ │ │ ├── NSURLRequest+DSL.h │ │ │ │ ├── NSURLRequest+DSL.m │ │ │ │ ├── NSURLRequest+LSHTTPRequest.h │ │ │ │ └── NSURLRequest+LSHTTPRequest.m │ │ │ └── NSURLSession │ │ │ │ ├── LSNSURLSessionHook.h │ │ │ │ └── LSNSURLSessionHook.m │ │ ├── LSNocilla.h │ │ ├── LSNocilla.m │ │ ├── Matchers │ │ │ ├── LSDataMatcher.h │ │ │ ├── LSDataMatcher.m │ │ │ ├── LSMatcheable.h │ │ │ ├── LSMatcher.h │ │ │ ├── LSMatcher.m │ │ │ ├── LSRegexMatcher.h │ │ │ ├── LSRegexMatcher.m │ │ │ ├── LSStringMatcher.h │ │ │ ├── LSStringMatcher.m │ │ │ ├── NSData+Matcheable.h │ │ │ ├── NSData+Matcheable.m │ │ │ ├── NSRegularExpression+Matcheable.h │ │ │ ├── NSRegularExpression+Matcheable.m │ │ │ ├── NSString+Matcheable.h │ │ │ └── NSString+Matcheable.m │ │ ├── Model │ │ │ ├── LSHTTPBody.h │ │ │ ├── LSHTTPRequest.h │ │ │ └── LSHTTPResponse.h │ │ ├── Nocilla.h │ │ └── Stubs │ │ │ ├── LSStubRequest.h │ │ │ ├── LSStubRequest.m │ │ │ ├── LSStubResponse.h │ │ │ └── LSStubResponse.m │ │ └── README.md └── KingfisherTests │ ├── DataReceivingSideEffectTests.swift │ ├── DiskStorageTests.swift │ ├── ImageCacheTests.swift │ ├── ImageDataProviderTests.swift │ ├── ImageDownloaderTests.swift │ ├── ImageDrawingTests.swift │ ├── ImageExtensionTests.swift │ ├── ImageModifierTests.swift │ ├── ImagePrefetcherTests.swift │ ├── ImageProcessorTests.swift │ ├── ImageViewExtensionTests.swift │ ├── Info.plist │ ├── KingfisherManagerTests.swift │ ├── KingfisherOptionsInfoTests.swift │ ├── KingfisherTestHelper.swift │ ├── KingfisherTests-Bridging-Header.h │ ├── LivePhotoSourceTests.swift │ ├── MemoryStorageTests.swift │ ├── NSButtonExtensionTests.swift │ ├── RetryStrategyTests.swift │ ├── StorageExpirationTests.swift │ ├── StringExtensionTests.swift │ ├── UIButtonExtensionTests.swift │ ├── Utils │ └── StubHelpers.swift │ ├── dancing-banana.gif │ └── single-frame.gif ├── fastlane ├── Fastfile └── actions │ ├── extract_current_change_log.rb │ ├── git_commit_all.rb │ ├── sync_build_number_to_git.rb │ └── update_change_log.rb └── images ├── kingfisher-1.jpg ├── kingfisher-10.jpg ├── kingfisher-2.jpg ├── kingfisher-3.jpg ├── kingfisher-4.jpg ├── kingfisher-5.jpg ├── kingfisher-6.jpg ├── kingfisher-7.jpg ├── kingfisher-8.jpg ├── kingfisher-9.jpg └── logo.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: onevcat 4 | open_collective: kingfisher 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### Check List 6 | 7 | Thanks for considering to open an issue. Before you submit your issue, please confirm these boxes are checked. 8 | 9 | - [ ] I have read the [wiki page](https://github.com/onevcat/Kingfisher/wiki) and [cheat sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet), but there is no information I need. 10 | - [ ] I have searched in [existing issues](https://github.com/onevcat/Kingfisher/issues?utf8=✓&q=is%3Aissue), but did not find a same one. 11 | - [ ] I want to report a problem instead of asking a question. It'd better to use [kingfisher tag in Stack Overflow](http://stackoverflow.com/questions/tagged/kingfisher) to ask a question. 12 | 13 | ### Issue Description 14 | 15 | #### What 16 | 17 | [Tell us about the issue] 18 | 19 | #### Reproduce 20 | 21 | [The steps to reproduce this issue. What is the url you were trying to load, where did you put your code, etc.] 22 | 23 | #### Other Comment 24 | 25 | [Add anything else here] 26 | 27 | 28 | 30 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | defaults: 4 | run: 5 | shell: bash -leo pipefail {0} 6 | 7 | on: [push, pull_request] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build-framework: 15 | runs-on: self-hosted 16 | strategy: 17 | matrix: 18 | destination: [ 19 | 'macOS', 20 | 'iOS Simulator,name=iPhone 15,OS=17.5', 21 | 'tvOS Simulator,name=Apple TV,OS=17.5', 22 | 'watchOS Simulator,name=Apple Watch Series 9 (41mm),OS=10.5' 23 | ] 24 | xcode: [ 25 | '15.2', 26 | '15.3', 27 | '16.0', 28 | '16.1', 29 | ] 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Install Gems 33 | run: bundle install 34 | - name: Build framework 35 | env: 36 | DESTINATION: platform=${{ matrix.destination }} 37 | XCODE_VERSION: ${{ matrix.xcode }} 38 | run: bundle exec fastlane build_ci 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | defaults: 4 | run: 5 | shell: bash -leo pipefail {0} 6 | 7 | on: [push, pull_request] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | run-tests: 15 | runs-on: self-hosted 16 | strategy: 17 | matrix: 18 | destination: [ 19 | 'macOS', 20 | 'iOS Simulator,name=iPhone 15,OS=17.5', 21 | 'tvOS Simulator,name=Apple TV,OS=17.5', 22 | 'watchOS Simulator,name=Apple Watch Series 9 (41mm),OS=10.5' 23 | ] 24 | xcode: [ 25 | '15.4', 26 | '16.2', 27 | ] 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Install Gems 31 | run: bundle install 32 | - name: Run tests 33 | env: 34 | DESTINATION: platform=${{ matrix.destination }} 35 | XCODE_VERSION: ${{ matrix.xcode }} 36 | run: bundle exec fastlane test_ci 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | # Xcode 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.xcuserstate 18 | *.hmap 19 | *.ipa 20 | .swiftpm 21 | 22 | # AppCode 23 | .idea 24 | 25 | # CocoaPods 26 | # 27 | # We recommend against adding the Pods directory to your .gitignore. However 28 | # you should judge for yourself, the pros and cons are mentioned at: 29 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 30 | # 31 | # Pods/ 32 | 33 | # Carthage 34 | # 35 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 36 | # Carthage/Checkouts 37 | 38 | Carthage/Build 39 | Kingfisher.framework.zip 40 | 41 | # macOS 42 | .DS_Store 43 | .AppleDouble 44 | .LSOverride 45 | 46 | # Icon must end with two \r 47 | Icon 48 | 49 | # Thumbnails 50 | ._* 51 | 52 | # Files that might appear in the root of a volume 53 | .DocumentRevisions-V100 54 | .fseventsd 55 | .Spotlight-V100 56 | .TemporaryItems 57 | .Trashes 58 | .VolumeIcon.icns 59 | 60 | # Directories potentially created on remote AFP share 61 | .AppleDB 62 | .AppleDesktop 63 | Network Trash Folder 64 | Temporary Items 65 | .apdisk 66 | images/logo.sketch 67 | 68 | # fastlane specific 69 | fastlane/report.xml 70 | 71 | # deliver temporary files 72 | fastlane/Preview.html 73 | 74 | # snapshot generated screenshots 75 | fastlane/screenshots/**/*.png 76 | fastlane/screenshots/screenshots.html 77 | 78 | # scan temporary files 79 | fastlane/test_output 80 | test_output 81 | fastlane/.env 82 | pre-change.yml 83 | .build 84 | fastlane/README.md 85 | /Kingfisher-TestImages 86 | 87 | .bundle/ 88 | vendor/ 89 | .vscode/settings.json 90 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.6 2 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Kingfisher] -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Kingfisher-Demo 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | import Kingfisher 29 | 30 | @UIApplicationMain 31 | class AppDelegate: UIResponder, UIApplicationDelegate { 32 | 33 | var window: UIWindow? 34 | 35 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 36 | return true 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 4.6.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1244 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/Resources/ImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/11/18. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | struct ImageLoader { 30 | static let sampleImageURLs: [URL] = { 31 | let prefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading" 32 | return (1...10).map { URL(string: "\(prefix)/kingfisher-\($0).jpg")! } 33 | }() 34 | 35 | static let orientationImageURLs: [URL] = { 36 | let prefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Orientation" 37 | return (1...16).map { URL(string: "\(prefix)/\($0).jpg")! } 38 | }() 39 | 40 | static let highResolutionImageURLs: [URL] = { 41 | let prefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/HighResolution" 42 | return (1...20).map { URL(string: "\(prefix)/\($0).jpg")! } 43 | }() 44 | 45 | static let gifImageURLs: [URL] = { 46 | let prefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/GIF" 47 | return (1...3).map { URL(string: "\(prefix)/\($0).gif")! } 48 | }() 49 | 50 | static let progressiveImageURL: URL = { 51 | let prefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Progressive" 52 | return URL(string: "\(prefix)/progressive.jpg")! 53 | }() 54 | 55 | static func roseImage(index: Int) -> URL { 56 | return URL(string: "https://github.com/onevcat/Flower-Data-Set/raw/master/rose/rose-\(index).jpg")! 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/Resources/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Demo/Demo/Kingfisher-Demo/Resources/loader.gif -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/AnimatedImageDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedImageDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by wangxingbin on 2021/4/27. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct AnimatedImageDemo: View { 32 | 33 | @State private var index = 1 34 | 35 | var url: URL { 36 | ImageLoader.gifImageURLs[index - 1] 37 | } 38 | 39 | var body: some View { 40 | VStack { 41 | KFAnimatedImage(url) 42 | .configure { view in 43 | view.framePreloadCount = 3 44 | } 45 | .cacheOriginalImage() 46 | .onSuccess { r in 47 | print("suc: \(r)") 48 | } 49 | .onFailure { e in 50 | print("err: \(e)") 51 | } 52 | .placeholder { p in 53 | ProgressView(p) 54 | } 55 | .fade(duration: 1) 56 | .forceTransition() 57 | .aspectRatio(contentMode: .fill) 58 | .frame(width: 300, height: 300) 59 | .cornerRadius(20) 60 | .shadow(radius: 5) 61 | .frame(width: 320, height: 320) 62 | 63 | Button(action: { 64 | self.index = (self.index % 3) + 1 65 | }) { Text("Next Image") } 66 | }.navigationBarTitle(Text("Basic Image"), displayMode: .inline) 67 | } 68 | 69 | } 70 | 71 | @available(iOS 14.0, *) 72 | struct AnimatedImageDemo_Previews: PreviewProvider { 73 | 74 | static var previews: some View { 75 | AnimatedImageDemo() 76 | } 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/GeometryReaderDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeometryReaderDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2021/06/12. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct GeometryReaderDemo: View { 32 | var body: some View { 33 | GeometryReader { geo in 34 | KFImage( 35 | ImageLoader.sampleImageURLs.first 36 | ) 37 | .placeholder { ProgressView() } 38 | .forceRefresh() 39 | .resizable() 40 | .scaledToFit() 41 | .frame(width: geo.size.width) 42 | } 43 | } 44 | } 45 | 46 | @available(iOS 14.0, *) 47 | struct GeometryReaderDemo_Previews: PreviewProvider { 48 | static var previews: some View { 49 | GeometryReaderDemo() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/GridDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2021/03/02. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | 29 | @available(iOS 14.0, *) 30 | struct GridDemo: View { 31 | 32 | @State var columns = [ 33 | GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) 34 | ] 35 | var body: some View { 36 | ScrollView { 37 | LazyVGrid(columns: columns) { 38 | ForEach(1..<700) { i in 39 | ImageCell(index: i).frame(height: columns.count == 1 ? 300 : 150) 40 | } 41 | } 42 | }.navigationBarTitle(Text("Grid")) 43 | .toolbar { 44 | ToolbarItem(placement: .navigationBarTrailing) { 45 | Button(action: { 46 | 47 | withAnimation(Animation.easeInOut(duration: 0.25)) { 48 | self.columns = Array(repeating: .init(.flexible()), count: self.columns.count % 4 + 1) 49 | } 50 | }) { 51 | Image(systemName: "square.grid.2x2") 52 | .font(.title) 53 | .foregroundColor(.primary) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | @available(iOS 14.0, *) 61 | struct GridDemo_Previews: PreviewProvider { 62 | static var previews: some View { 63 | GridDemo() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/LazyVStackDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LazyVStackDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2021/03/02. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct LazyVStackDemo: View { 32 | @State private var singleImage = false 33 | 34 | var body: some View { 35 | ScrollView { 36 | // Checking for #1839 37 | Toggle("Single Image", isOn: $singleImage).padding() 38 | LazyVStack { 39 | ForEach(1..<700) { i in 40 | if singleImage { 41 | KFImage.url(ImageLoader.roseImage(index: 1)) 42 | } else { 43 | ImageCell(index: i).frame(width: 300, height: 300) 44 | } 45 | } 46 | } 47 | }.navigationBarTitle(Text("Lazy Stack"), displayMode: .inline) 48 | } 49 | } 50 | 51 | @available(iOS 14.0, *) 52 | struct LazyVStackDemo_Previews: PreviewProvider { 53 | static var previews: some View { 54 | LazyVStackDemo() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/ProgressiveJPEGDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressiveJPEGDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2025/03/03. 6 | // 7 | // Copyright (c) 2025 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Kingfisher 28 | import SwiftUI 29 | 30 | @available(iOS 14.0, *) 31 | struct ProgressiveJPEGDemo: View { 32 | 33 | @State private var totalSize: Int64? 34 | @State private var receivedSize: Int64? 35 | 36 | var body: some View { 37 | KFImage(ImageLoader.progressiveImageURL) 38 | .progressiveJPEG() 39 | .onProgress({ receivedSize, totalSize in 40 | self.totalSize = totalSize 41 | self.receivedSize = receivedSize 42 | }) 43 | .resizable() 44 | .frame(width: 300, height: 300) 45 | if let totalSize = totalSize, let receivedSize = receivedSize { 46 | Text("Received: \(receivedSize) / \(totalSize)") 47 | } 48 | } 49 | } 50 | 51 | @available(iOS 14.0, *) 52 | struct ProgressiveJPEGDemo_Previews : PreviewProvider { 53 | static var previews: some View { 54 | ProgressiveJPEGDemo() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/Regression/Issue1998View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleListDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2022/09/21. 6 | // 7 | // Copyright (c) 2022 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct Issue1998View: View { 32 | var body: some View { 33 | Text("This is a test case for #1988") 34 | 35 | List { 36 | ForEach(1...100, id: \.self) { idx in 37 | KFImage(ImageLoader.sampleImageURLs.first) 38 | .startLoadingBeforeViewAppear() 39 | .resizable() 40 | .frame(width: 48, height: 48) 41 | } 42 | } 43 | } 44 | } 45 | 46 | @available(iOS 14.0, *) 47 | struct SingleListDemo_Previews: PreviewProvider { 48 | static var previews: some View { 49 | Issue1998View() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/Regression/Issue2035View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Issue2035View.swift 3 | // Kingfisher 4 | // 5 | // Created by jp20028 on 2023/02/23. 6 | // 7 | // Copyright (c) 2023 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct Issue2035View: View { 32 | var body: some View { 33 | KFImage(nil) 34 | .startLoadingBeforeViewAppear() 35 | .onSuccess { _ in 36 | print("Done") 37 | } 38 | .onFailure { err in 39 | print(err) 40 | } 41 | } 42 | } 43 | 44 | @available(iOS 14.0, *) 45 | struct Issue2035View_Previews: PreviewProvider { 46 | static var previews: some View { 47 | Issue2035View() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/Regression/Issue2352View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Issue2352View.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2025/02/04. 6 | // 7 | // Copyright (c) 2025 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct Issue2352View: View { 32 | var body: some View { 33 | List { 34 | ForEach(0..<40, id: \.self) { row in 35 | KFAnimatedImage 36 | .url( 37 | URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/refs/heads/master/DemoAppImage/GIF/jumping.gif")! 38 | ) 39 | .backgroundDecode() 40 | .scaleFactor(UIScreen.main.scale) 41 | .scaledToFill() 42 | .frame(width: 50, height: 50) 43 | .clipShape(.circle) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/SizingAnimationDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SizingAnimationDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2021/03/02. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct SizingAnimationDemo: View { 32 | @State var imageSize: CGFloat = 250 33 | @State var isPlaying = false 34 | 35 | var body: some View { 36 | VStack { 37 | KFImage(URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher-1.jpg")!) 38 | .resizable() 39 | .aspectRatio(contentMode: .fill) 40 | .frame(width: imageSize, height: imageSize) 41 | .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) 42 | .frame(width: 350, height: 350) 43 | Button(action: { 44 | playButtonAction() 45 | }) { 46 | Image(systemName: self.isPlaying ? "pause.fill" : "play.fill") 47 | .font(.system(size: 60)) 48 | } 49 | } 50 | 51 | } 52 | func playButtonAction() { 53 | withAnimation(Animation.spring(response: 0.45, dampingFraction: 0.475, blendDuration: 0)) { 54 | if self.imageSize == 250 { 55 | self.imageSize = 350 56 | } else { 57 | self.imageSize = 250 58 | } 59 | self.isPlaying.toggle() 60 | } 61 | } 62 | } 63 | 64 | @available(iOS 14.0, *) 65 | struct SizingAnimationDemo_Previews: PreviewProvider { 66 | static var previews: some View { 67 | SizingAnimationDemo() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/SwiftUIViews/TransitionViewDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransitionViewDemo.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2021/08/03. 6 | // 7 | // Copyright (c) 2021 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import Kingfisher 29 | 30 | @available(iOS 14.0, *) 31 | struct TransitionViewDemo: View { 32 | @State private var showDetails = false 33 | 34 | var body: some View { 35 | VStack { 36 | Button(showDetails ? "Hide" : "Show") { 37 | withAnimation { 38 | showDetails.toggle() 39 | } 40 | } 41 | if showDetails { 42 | KFImage(ImageLoader.sampleImageURLs.first) 43 | .transition(.slide) 44 | } 45 | Spacer() 46 | }.frame(height: 500) 47 | } 48 | } 49 | 50 | @available(iOS 14.0, *) 51 | struct TransitionViewDemo_Previews: PreviewProvider { 52 | static var previews: some View { 53 | TransitionViewDemo() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/AVAssetImageGeneratorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetImageGeneratorViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2020/08/09. 6 | // 7 | // Copyright (c) 2020 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | import AVKit 29 | import Kingfisher 30 | 31 | class AVAssetImageGeneratorViewController: UIViewController { 32 | @IBOutlet weak var imageView: UIImageView! 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | let provider = AVAssetImageDataProvider( 38 | assetURL: URL(string: "https://github.com/onevcat/sample-files/raw/main/video/mp4/astronaut_flying_fantasy.mp4")!, 39 | seconds: 6.0 40 | ) 41 | KF.dataProvider(provider).set(to: imageView) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/DetailImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailImageViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/11/25. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | 29 | class DetailImageViewController: UIViewController { 30 | 31 | var imageURL: URL! 32 | @IBOutlet weak var imageView: UIImageView! 33 | @IBOutlet weak var scrollView: UIScrollView! 34 | @IBOutlet weak var infoLabel: UILabel! 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | scrollView.delegate = self 39 | 40 | imageView.kf.setImage(with: imageURL, options: [.memoryCacheExpiration(.expired)]) { result in 41 | guard let image = try? result.get().image else { 42 | return 43 | } 44 | let scrollViewFrame = self.scrollView.frame 45 | let scaleWidth = scrollViewFrame.size.width / image.size.width 46 | let scaleHeight = scrollViewFrame.size.height / image.size.height 47 | let minScale = min(scaleWidth, scaleHeight) 48 | self.scrollView.minimumZoomScale = minScale 49 | DispatchQueue.main.async { 50 | self.scrollView.zoomScale = minScale 51 | } 52 | 53 | self.infoLabel.text = "\(image.size)" 54 | } 55 | } 56 | } 57 | 58 | extension DetailImageViewController: UIScrollViewDelegate { 59 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 60 | return imageView 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/GIFViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GIFViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/11/25. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | import Kingfisher 29 | 30 | class GIFViewController: UIViewController { 31 | 32 | @IBOutlet weak var imageView: UIImageView! 33 | @IBOutlet weak var animatedImageView: AnimatedImageView! 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | let url = ImageLoader.gifImageURLs.last! 38 | 39 | // Should need to use different cache key to prevent data overwritten by each other. 40 | KF.url(url, cacheKey: "\(url)-imageview").set(to: imageView) 41 | KF.url(url, cacheKey: "\(url)-animated_imageview").set(to: animatedImageView) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/ImageCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCollectionViewCell.swift 3 | // Kingfisher-Demo 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | 29 | class ImageCollectionViewCell: UICollectionViewCell { 30 | 31 | @IBOutlet weak var cellImageView: UIImageView! 32 | 33 | #if os(tvOS) 34 | override func awakeFromNib() { 35 | super.awakeFromNib() 36 | 37 | cellImageView.adjustsImageWhenAncestorFocused = true 38 | cellImageView.clipsToBounds = false 39 | } 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/InfinityCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfinityCollectionViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2018/11/19. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | import Kingfisher 29 | 30 | private let reuseIdentifier = "InfinityCell" 31 | 32 | class InfinityCollectionViewController: UICollectionViewController { 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | title = "Infinity" 37 | setupOperationNavigationBar() 38 | } 39 | 40 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 41 | return 1 42 | } 43 | 44 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 45 | return 10000000 46 | } 47 | 48 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 49 | let cell = collectionView 50 | .dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ImageCollectionViewCell 51 | let urls = ImageLoader.sampleImageURLs 52 | let url = urls[indexPath.row % urls.count] 53 | 54 | // Mark each row as a new image. 55 | let resource = KF.ImageResource(downloadURL: url, cacheKey: "key-\(indexPath.row)") 56 | KF.resource(resource).set(to: cell.cellImageView) 57 | 58 | return cell 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/11/18. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | 29 | class MainViewController: UITableViewController { 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | title = "Kingfisher" 33 | setupOperationNavigationBar() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/SwiftUIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2020/12/16. 6 | // 7 | // Copyright (c) 2020 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import SwiftUI 28 | import UIKit 29 | 30 | @available(iOS 14.0, *) 31 | class SwiftUIViewController: UIHostingController { 32 | required init?(coder: NSCoder) { 33 | super.init(coder: coder, rootView: MainView()) 34 | } 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-Demo/ViewControllers/TextAttachmentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextAttachmentViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2020/08/07. 6 | // 7 | // Copyright (c) 2020 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | import Kingfisher 29 | 30 | class TextAttachmentViewController: UIViewController { 31 | @IBOutlet weak var label: UILabel! 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | title = "Text Attachment" 37 | setupOperationNavigationBar() 38 | 39 | loadAttributedText() 40 | } 41 | 42 | private func loadAttributedText() { 43 | let attributedText = NSMutableAttributedString(string: "Hello World") 44 | 45 | let textAttachment = NSTextAttachment() 46 | attributedText.replaceCharacters(in: NSRange(), with: NSAttributedString(attachment: textAttachment)) 47 | label.attributedText = attributedText 48 | 49 | let label = getLabel() 50 | KF.url(URL(string: "https://onevcat.com/assets/images/avatar.jpg")!) 51 | .resizing(referenceSize: CGSize(width: 30, height: 30)) 52 | .roundCorner(radius: .point(15)) 53 | .set(to: textAttachment, attributedView: label) 54 | } 55 | 56 | func getLabel() -> UILabel { 57 | return label 58 | } 59 | } 60 | 61 | extension TextAttachmentViewController: MainDataViewReloadable { 62 | func reload() { 63 | label.attributedText = NSAttributedString(string: "-") 64 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 65 | self.loadAttributedText() 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-macOS-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Kingfisher-macOS-Demo 4 | // 5 | // Created by Wei Wang on 16/1/6. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Cocoa 28 | import Kingfisher 29 | 30 | @NSApplicationMain 31 | class AppDelegate: NSObject, NSApplicationDelegate { 32 | 33 | func applicationDidFinishLaunching(aNotification: NSNotification) { 34 | // Insert code here to initialize your application 35 | } 36 | 37 | func applicationWillTerminate(aNotification: NSNotification) { 38 | // Insert code here to tear down your application 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-macOS-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-macOS-Demo/Cell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-macOS-Demo/GIFHeavyViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GIFHeavyViewController.swift 3 | // Kingfisher 4 | // 5 | // Created by yeatse on 2024/1/7. 6 | // 7 | // Copyright (c) 2024 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Cocoa 28 | import Kingfisher 29 | 30 | class GIFHeavyViewController: NSViewController { 31 | @IBOutlet weak var stackView: NSStackView! 32 | 33 | let imageViews = [ 34 | AnimatedImageView(), 35 | AnimatedImageView(), 36 | AnimatedImageView(), 37 | AnimatedImageView(), 38 | ] 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/GIF/GifHeavy.gif") 44 | 45 | for imageView in imageViews { 46 | stackView.addArrangedSubview(imageView) 47 | imageView.translatesAutoresizingMaskIntoConstraints = false 48 | imageView.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true 49 | imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) 50 | imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) 51 | imageView.imageScaling = .scaleProportionallyDown 52 | } 53 | stackView.layoutSubtreeIfNeeded() 54 | for imageView in imageViews { 55 | imageView.kf.setImage(with: url) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-macOS-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 4.6.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1244 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2019 Wei Wang. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-tvOS-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 4.6.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1244 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo Extension/Assets.xcassets/README__ignoredByTemplate__: -------------------------------------------------------------------------------- 1 | Did you know that git does not support storing empty directories? 2 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo Extension/ExtensionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.swift 3 | // Kingfisher-watchOS-Demo Extension 4 | // 5 | // Created by Wei Wang on 16/1/19. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import WatchKit 28 | 29 | class ExtensionDelegate: NSObject, WKExtensionDelegate { 30 | 31 | func applicationDidFinishLaunching() { 32 | // Perform any final initialization of your application. 33 | } 34 | 35 | func applicationDidBecomeActive() { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillResignActive() { 40 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 41 | // Use this method to pause ongoing tasks, disable timers, etc. 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Kingfisher-watchOS-Demo Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 4.6.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1244 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | com.onevcat.Kingfisher-Demo.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | RemoteInterfacePrincipalClass 36 | $(PRODUCT_MODULE_NAME).InterfaceController 37 | WKExtensionDelegateClassName 38 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 39 | 40 | 41 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo Extension/InterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.swift 3 | // Kingfisher-watchOS-Demo Extension 4 | // 5 | // Created by Wei Wang on 16/1/19. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | 28 | import WatchKit 29 | import Foundation 30 | import Kingfisher 31 | 32 | @MainActor var count = 0 33 | 34 | class InterfaceController: WKInterfaceController { 35 | 36 | @IBOutlet var interfaceImage: WKInterfaceImage! 37 | 38 | var currentIndex: Int? 39 | 40 | 41 | override func awake(withContext context: Any?) { 42 | super.awake(withContext: context) 43 | 44 | currentIndex = count 45 | count += 1 46 | } 47 | 48 | func refreshImage() { 49 | let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(currentIndex! + 1).jpg")! 50 | print("Start loading... \(url)") 51 | interfaceImage.kf.setImage(with: url, completionHandler: { r in 52 | print(r) 53 | }) 54 | } 55 | 56 | override func willActivate() { 57 | // This method is called when watch view controller is about to be visible to user 58 | super.willActivate() 59 | refreshImage() 60 | } 61 | 62 | override func didDeactivate() { 63 | // This method is called when watch view controller is no longer visible 64 | super.didDeactivate() 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "44x44", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "appLauncher", 41 | "subtype" : "40mm" 42 | }, 43 | { 44 | "size" : "50x50", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "appLauncher", 48 | "subtype" : "44mm" 49 | }, 50 | { 51 | "size" : "86x86", 52 | "idiom" : "watch", 53 | "scale" : "2x", 54 | "role" : "quickLook", 55 | "subtype" : "38mm" 56 | }, 57 | { 58 | "size" : "98x98", 59 | "idiom" : "watch", 60 | "scale" : "2x", 61 | "role" : "quickLook", 62 | "subtype" : "42mm" 63 | }, 64 | { 65 | "size" : "108x108", 66 | "idiom" : "watch", 67 | "scale" : "2x", 68 | "role" : "quickLook", 69 | "subtype" : "44mm" 70 | }, 71 | { 72 | "idiom" : "watch-marketing", 73 | "size" : "1024x1024", 74 | "scale" : "1x" 75 | } 76 | ], 77 | "info" : { 78 | "version" : 1, 79 | "author" : "xcode" 80 | } 81 | } -------------------------------------------------------------------------------- /Demo/Demo/Kingfisher-watchOS-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Kingfisher-Demo 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 4.6.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1244 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | com.onevcat.Kingfisher-Demo 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Demo/Kingfisher-Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Kingfisher-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Kingfisher-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "fastlane" 6 | gem "cocoapods" 7 | gem "xcode-install" -------------------------------------------------------------------------------- /Kingfisher.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "Kingfisher" 4 | s.version = "8.3.2" 5 | s.summary = "A lightweight and pure Swift implemented library for downloading and cacheing image from the web." 6 | 7 | s.description = <<-DESC 8 | Kingfisher is a powerful and pure Swift implemented library for downloading and cacheing image from the web. It provides you a chance to use pure Swift alternation in your next app. 9 | 10 | * Everything in Kingfisher goes asynchronously, not only downloading, but also caching. That means you can never worry about blocking your UI thread. 11 | * Multiple-layer cache. Downloaded image will be cached in both memory and disk. So there is no need to download it again and this could boost your app dramatically. 12 | * Cache management. You can set the max duration or size the cache could take. And the cache will also be cleaned automatically to prevent taking too much resource. 13 | * Modern framework. Kingfisher uses `NSURLSession` and the latest technology of GCD, which makes it a strong and swift framework. It also provides you easy APIs to use. 14 | * Cancellable processing task. You can cancel the downloading or retriving image process if it is not needed anymore. 15 | * Independent components. You can use the downloader or caching system separately. Or even create your own cache based on Kingfisher's code. 16 | * Options to decompress the image in background before render it, which could improve the UI performance. 17 | * A category over `UIImageView` for setting image from an url directly. 18 | DESC 19 | 20 | s.homepage = "https://github.com/onevcat/Kingfisher" 21 | s.screenshots = "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/logo.png" 22 | 23 | s.license = { :type => "MIT", :file => "LICENSE" } 24 | 25 | s.authors = { "onevcat" => "onevcat@gmail.com" } 26 | s.social_media_url = "https://github.com/onevcat" 27 | 28 | s.swift_versions = ['5.0'] 29 | 30 | s.ios.deployment_target = "13.0" 31 | s.tvos.deployment_target = "13.0" 32 | s.osx.deployment_target = "10.15" 33 | s.watchos.deployment_target = "6.0" 34 | s.visionos.deployment_target = "1.0" 35 | 36 | s.source = { :git => "https://github.com/onevcat/Kingfisher.git", :tag => s.version } 37 | s.source_files = ["Sources/**/*.swift"] 38 | s.resource_bundles = {"Kingfisher" => ["Sources/PrivacyInfo.xcprivacy"]} 39 | s.pod_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } 40 | 41 | s.requires_arc = true 42 | s.frameworks = "CFNetwork", "Accelerate" 43 | s.weak_frameworks = "SwiftUI", "Combine" 44 | end 45 | -------------------------------------------------------------------------------- /Kingfisher.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Kingfisher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Kingfisher.xcodeproj/xcshareddata/xcbaselines/D1ED2D3E1AD2D09F00CFC3EB.xcbaseline/74237B0B-7981-4A24-B6C4-95F4A5E7727F.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | ImageCacheTests 8 | 9 | testRetrivingImagePerformance() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.27 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Kingfisher.xcodeproj/xcshareddata/xcbaselines/D1ED2D3E1AD2D09F00CFC3EB.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 74237B0B-7981-4A24-B6C4-95F4A5E7727F 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 3500 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | iMac14,2 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone7,2 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Kingfisher.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Kingfisher.xcworkspace/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // ___FILENAME___ 8 | // Kingfisher 9 | // 10 | // Created by ___USERNAME___ on ___DATE___. 11 | // 12 | // Copyright (c) ___YEAR___ Wei Wang <onevcat@gmail.com> 13 | // 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy 15 | // of this software and associated documentation files (the "Software"), to deal 16 | // in the Software without restriction, including without limitation the rights 17 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the Software is 19 | // furnished to do so, subject to the following conditions: 20 | // 21 | // The above copyright notice and this permission notice shall be included in 22 | // all copies or substantial portions of the Software. 23 | // 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | // THE SOFTWARE. 31 | 32 | 33 | -------------------------------------------------------------------------------- /Kingfisher.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Kingfisher.xcworkspace/xcshareddata/Kingfisher.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "17FB51EC7B87DC25DD21E28FDFD877B479CFFAC1", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "EDD4D5CA2765BF1400F1A6D6680AADA3F565AD7A" : 9223372036854775807, 8 | "17FB51EC7B87DC25DD21E28FDFD877B479CFFAC1" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "A97B351B-F1BD-4510-8138-460C0D267EF0", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "EDD4D5CA2765BF1400F1A6D6680AADA3F565AD7A" : "Kingfisher\/Kingfisher-TestImages\/", 13 | "17FB51EC7B87DC25DD21E28FDFD877B479CFFAC1" : "Kingfisher\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Kingfisher", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Kingfisher.xcworkspace", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:onevcat\/Kingfisher.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "17FB51EC7B87DC25DD21E28FDFD877B479CFFAC1" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:onevcat\/Kingfisher-TestImages.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EDD4D5CA2765BF1400F1A6D6680AADA3F565AD7A" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Kingfisher.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Wei Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Kingfisher", 6 | platforms: [.iOS(.v13), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6)], 7 | products: [ 8 | .library(name: "Kingfisher", targets: ["Kingfisher"]) 9 | ], 10 | targets: [ 11 | .target( 12 | name: "Kingfisher", 13 | path: "Sources" 14 | ) 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /Package@swift-5.9.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Kingfisher", 6 | platforms: [ 7 | .iOS(.v13), 8 | .macOS(.v10_15), 9 | .tvOS(.v13), 10 | .watchOS(.v6), 11 | .visionOS(.v1) 12 | ], 13 | products: [ 14 | .library(name: "Kingfisher", targets: ["Kingfisher"]) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "Kingfisher", 19 | path: "Sources", 20 | resources: [.process("PrivacyInfo.xcprivacy")] 21 | ) 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ../../CHANGELOG.md -------------------------------------------------------------------------------- /Sources/Documentation.docc/CommonTasks/CommonTasks_Serializer.md: -------------------------------------------------------------------------------- 1 | # Common Tasks - Serializer 2 | 3 | ``CacheSerializer`` is utilized to convert data into an image object for retrieval from disk cache, and conversely, 4 | for storing images to the disk cache. 5 | 6 | ### Use the default serializer 7 | 8 | ```swift 9 | // Just without anything 10 | imageView.kf.setImage(with: url) 11 | // It equals to 12 | imageView.kf.setImage(with: url, options: [.cacheSerializer(DefaultCacheSerializer.default)]) 13 | ``` 14 | 15 | ``DefaultCacheSerializer`` is responsible for converting cached data into a corresponding image object and vice versa. 16 | It supports PNG, JPEG, and GIF formats by default. 17 | 18 | ### Enforce a format 19 | 20 | To enforce a specific image format, use ``FormatIndicatedCacheSerializer``, which offers serializers for all supported 21 | formats: ``FormatIndicatedCacheSerializer/png``, ``FormatIndicatedCacheSerializer/jpeg``, and 22 | ``FormatIndicatedCacheSerializer/gif``. 23 | 24 | #### Use PNG serializer when rounding image corner 25 | 26 | While ``DefaultCacheSerializer`` aims to preserve the original format of input image data, there are scenarios where 27 | this behavior might not meet your needs. For example, when using a ``RoundCornerImageProcessor``, it's often desirable 28 | to maintain an alpha channel for transparency around the corners. JPEG images, lacking an alpha channel, would not 29 | support this transparency when saved. To ensure the presence of an alpha channel by converting images to PNG, you can 30 | set the PNG serializer explicitly: 31 | 32 | ```swift 33 | let roundCorner = RoundCornerImageProcessor(cornerRadius: 20) 34 | imageView.kf.setImage(with: url, 35 | options: [.processor(roundCorner), 36 | .cacheSerializer(FormatIndicatedCacheSerializer.png)] 37 | ) 38 | ``` 39 | 40 | ### Creating customized serializer 41 | 42 | Make a type conforming to `CacheSerializer` by implementing `data(with:original:)` and `image(with:options:)`: 43 | To create a type that conforms to ``CacheSerializer``, implement the ``CacheSerializer/data(with:original:)`` 44 | and ``CacheSerializer/image(with:options:)``: 45 | 46 | ```swift 47 | struct MyCacheSerializer: CacheSerializer { 48 | func data(with image: Image, original: Data?) -> Data? { 49 | return MyFramework.data(of: image) 50 | } 51 | 52 | func image(with data: Data, options: KingfisherParsedOptionsInfo?) -> Image? { 53 | return MyFramework.createImage(from: data) 54 | } 55 | } 56 | ``` 57 | 58 | Then pass it to the ``KingfisherWrapper/setImage(with:placeholder:options:completionHandler:)-9h820`` methods: 59 | 60 | ```swift 61 | let serializer = MyCacheSerializer() 62 | let url = URL(string: "https://yourdomain.com/example.png") 63 | imageView.kf.setImage(with: url, options: [.cacheSerializer(serializer)]) 64 | ``` 65 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Documentation.md: -------------------------------------------------------------------------------- 1 | # ``Kingfisher`` 2 | 3 | @Metadata { 4 | @PageImage( 5 | purpose: icon, 6 | source: "logo", 7 | alt: "The logo icon of Kingfisher") 8 | @PageColor(blue) 9 | } 10 | 11 | A lightweight, pure-Swift library for downloading and caching images from the web. 12 | 13 | ## Overview 14 | 15 | Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance 16 | to use a pure-Swift way to work with remote images in your next app, regardless you are using UIKit, AppKit or SwiftUI. 17 | 18 | With Kingfisher, you can easily: 19 | 20 | - **Download** the images from a remote URL and display it in an image view or button. 21 | - **Cache** the images in both the memory and the disk. When loading for the next time, it shows immediately without 22 | downloading again. 23 | - **Process** the downloaded images with pre-defined or customized processors. 24 | 25 | ### Featured 26 | 27 | @Links(visualStyle: detailedGrid) { 28 | - 29 | - 30 | } 31 | 32 | 33 | ## Topics 34 | 35 | ### Essentials 36 | 37 | - 38 | - 39 | 40 | ### Loading Images in Simple Way 41 | 42 | - ``KingfisherCompatible`` 43 | - ``KingfisherWrapper/setImage(with:placeholder:options:completionHandler:)-8qfkr`` 44 | - ``KingfisherManager`` 45 | - ``Source`` 46 | 47 | ### Loading Options 48 | 49 | - ``KingfisherOptionsInfoItem`` 50 | 51 | ### Image Downloader 52 | 53 | - 54 | - ``ImageDownloader`` 55 | - ``ImagePrefetcher`` 56 | - ``DownloadTask`` 57 | 58 | ### Image Processor 59 | 60 | @Links(visualStyle: detailedGrid) { 61 | - 62 | - ``ImageProcessor`` 63 | } 64 | 65 | ### Image Cache & Serializer 66 | 67 | - 68 | - 69 | - ``ImageCache`` 70 | - ``CacheSerializer`` 71 | 72 | ### GIF 73 | 74 | - ``AnimatedImageView`` 75 | - ``GIFAnimatedImage`` 76 | 77 | ### Live Photo 78 | 79 | - 80 | - ``KingfisherWrapper/setImage(with:options:completionHandler:)-1to8a`` 81 | 82 | ### SwiftUI 83 | 84 | - ``KFImage`` 85 | 86 | ### Help & Communication 87 | 88 | - 89 | - 90 | 91 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | @Metadata { 4 | @PageImage(purpose: card, source: "getting-started-card")) 5 | @PageColor(blue) 6 | } 7 | 8 | Installs Kingfisher to your project, setup everything and some starter examples of the core functionality. 9 | 10 | ## Overview 11 | 12 | Kingfisher is designed to facilitate the downloading and caching of remote images in the simplest way possible. 13 | As such, the basic usage of Kingfisher is straightforward. We offer two step-by-step tutorials to help you understand 14 | and utilize Kingfisher's fundamental features in both UIKit and SwiftUI environments. The tutorials will cover the 15 | following aspects: 16 | 17 | ##### Installing Kingfisher 18 | Learn how to integrate Kingfisher into your project setup. 19 | 20 | ##### Loading and Displaying Images 21 | Discover how to effortlessly fetch and display images from remote URLs using convenient view extensions. 22 | 23 | ##### Processing Images with Processors 24 | 25 | Understand how to manipulate and transform images using the ImageProcessor functionality. 26 | 27 | ##### Inspecting and Managing Image Cache 28 | 29 | Gain insights into how to check the image cache status and handle image caching. 30 | 31 | ## Tutorials 32 | 33 | By following these tutorials, you will acquire a preliminary understanding of Kingfisher, laying the groundwork for 34 | potential advanced usage in the future. 35 | 36 | Choose the approach you prefer to begin the tutorial (UIKit or SwiftUI): 37 | 38 | @Links(visualStyle: list) { 39 | - 40 | - 41 | } 42 | 43 | > tip: In addition to UIKit and SwiftUI, Kingfisher also offers support for use in AppKit. This extends Kingfisher's 44 | > versatility across different Apple platforms, providing a unified API for handling remote images. 45 | > 46 | > If you are interested in utilizing Kingfisher within an AppKit context, we recommend referring to the UIKit 47 | > tutorials as a starting point. Most of the concept, even the APIs, are shared. 48 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/MigrationGuide.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | How to migrate from an earlier version of Kingfisher to the latest one. 4 | 5 | @Links(visualStyle: list) { 6 | - 7 | } 8 | 9 | 10 | ### Archived 11 | 12 | If you are still using an even earlier version, check the archived guide below and follow them to migrate to v7 first. 13 | 14 | @Links(visualStyle: list) { 15 | - 16 | - 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/MigrationGuide/Migration-To-6.md: -------------------------------------------------------------------------------- 1 | # Migrating from v5 to v6 2 | 3 | Migrating Kingfisher from version 5 to version 6. 4 | 5 | ## Overview 6 | 7 | Kingfisher 6.0 contains some breaking changes if you want to upgrade from the previous version. 8 | 9 | Depending on your use cases of Kingfisher 5.x, it may take no effort or at most several minutes to fix errors and warnings after upgrading. If you are not using Kingfisher with SwiftUI, and have no warnings in your code related to Kingfisher, then you are already done and feel free to upgrade to the latest version. Otherwise, please read the sections below before performing the upgrade. 10 | 11 | ### SwiftUI support 12 | 13 | Kingfisher started to support SwiftUI from [5.8.0](https://github.com/onevcat/Kingfisher/releases/tag/5.8.0). At that time, a new framework was added to handle all SwiftUI-related things. Search for `KingfisherSwiftUI` in your SwiftUI code, or check if there is a `Kingfisher/SwiftUI` entry in your Podfile. If there is, then you need to perform some change of the integrating way before continuing. 14 | 15 | In Kingfisher 6, to make the project structure simpler, as well as treat SwiftUI as the first citizen in the library, we combined the library for SwiftUI into the main Kingfisher target. 16 | 17 | That means, there is no `KingfisherSwiftUI` or `Kingfisher/SwiftUI` anymore. If you installed it through: 18 | 19 | - Carthage: Remove `KingfisherSwiftUI` from "Linked Frameworks and Libraries" and all "KingfisherSwiftUI.framework" related lines from the "copy-framework". 20 | - CocoaPods: Remove `pod 'Kingfisher/SwiftUI'` from your Podfile. To continue using Kingfisher, you still need to keep or add back `pod 'Kingfisher'` entry. Then, run `pod install` again. 21 | - Swift Package Manager: Since now there is only one framework, all the old "static" and "dynamic" variants are removed. We suggest a clean reinstallation for the new version. Check the [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) for more. 22 | 23 | When it is done, you can now replace any `import KingfisherSwiftUI` with `import Kingfisher`. 24 | 25 | ### Removing legacy deprecated code 26 | 27 | All deprecated types, methods and properties are removed from the code base. Before upgrading, please make sure there is no warnings left in your project which complain the using of deprecated code. All deprecated things have replacement and with the help of warning message, adapting to new code should be easy enough. 28 | 29 | If you are curious about what are exactly removed, check [these commits](https://github.com/onevcat/Kingfisher/pull/1525/files). 30 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/MigrationGuide/Migration-To-7.md: -------------------------------------------------------------------------------- 1 | # Migrating from v6 to v7 2 | 3 | Migrating Kingfisher from version 6 to version 7. 4 | 5 | ## Overview 6 | 7 | Kingfisher 7.0 contains some breaking changes if you want to upgrade from the previous version. In this documentation, we will cover most of the noticeable API changes. 8 | 9 | ### Deploy target 10 | 11 | The UIKit/AppKit part of Kingfisher now supports from: 12 | 13 | - iOS 12.0 14 | - macOS 10.14 15 | - tvOS 12.0 16 | - watchOS 5.0 17 | 18 | > We do not have proper simulator support or device of versions before those. So dropping any older versions give us a chance to make sure the project works properly on all supported versions. This also fixes a compiling issue when building with Xcode 13 with SPM. 19 | 20 | The SwiftUI part of Kingfisher now supports from 21 | 22 | - iOS 14 23 | - macOS 11.0 24 | - tvOS 14.0 25 | - watchOS 7.0 26 | 27 | > On iOS 13, there is no `@StateObject` property wrapper, which makes it very tricky when loading data properly across difference view body evaluating. For a stable data model in Kingfisher's SwiftUI, we need to drop iOS 13 and all other platform versions from the same year. 28 | 29 | ### Migration Steps 30 | 31 | The main breaking changes happens to the SwiftUI support. By following the steps you should be able to migrate to the new version. 32 | 33 | - Make sure you do not have any warning from Kingfisher. All previous deprecated methods and properties are removed in version 7. If you are still using some of the deprecated methods, follow the help message to fix them first before migrating. 34 | - The original ``KFImage`` initializers: `init(source:isLoaded:)` and `init(_:isLoaded:)` are removed. Or strictly speaking, the `isLoaded` parameter is removed. If you are not using the `isLoaded` binding before, the transition to the new initializer ``KFImage/init(source:)`` and ``KFImage/init(_:)`` is transparent. 35 | - The `isLoaded` binding was a mis-use of binding and it did not do what is expected. If you need to get a state of loading of a ``KFImage``, change a `@State` yourself in the related ``KFImage`` lifecycle modifier: such as ``KFImage/onSuccess(_:)`` and ``KFImage/onFailure(_:)``. 36 | - All of the `isLoaded` parameter are also removed from the chain-able ``KF`` shorthand. 37 | - If you are using ``KFImage/loadImmediately(_:)`` to get workaround of [#1660](https://github.com/onevcat/Kingfisher/issues/1660), it is not necessary in the new version anymore. You will have a warning and please just remove it. 38 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-SampleCell-1.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SampleCell: UITableViewCell { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-SampleCell-2.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SampleCell: UITableViewCell { 4 | var sampleImageView: UIImageView = { 5 | let imageView = UIImageView(frame: .zero) 6 | imageView.translatesAutoresizingMaskIntoConstraints = false 7 | return imageView 8 | }() 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-SampleCell-3.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SampleCell: UITableViewCell { 4 | var sampleImageView: UIImageView = { 5 | let imageView = UIImageView(frame: .zero) 6 | imageView.translatesAutoresizingMaskIntoConstraints = false 7 | return imageView 8 | }() 9 | 10 | var sampleLabel: UILabel = { 11 | let label = UILabel(frame: .zero) 12 | label.translatesAutoresizingMaskIntoConstraints = false 13 | return label 14 | }() 15 | 16 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 17 | super.init(style: style, reuseIdentifier: reuseIdentifier) 18 | 19 | contentView.addSubview(sampleImageView) 20 | NSLayoutConstraint.activate([ 21 | sampleImageView.widthAnchor.constraint(equalToConstant: 64), 22 | sampleImageView.heightAnchor.constraint(equalToConstant: 64), 23 | sampleImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12), 24 | sampleImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) 25 | ]) 26 | 27 | contentView.addSubview(sampleLabel) 28 | NSLayoutConstraint.activate([ 29 | sampleLabel.leadingAnchor.constraint(equalTo: sampleImageView.trailingAnchor, constant: 12), 30 | sampleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) 31 | ]) 32 | 33 | } 34 | 35 | required init?(coder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-1.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ViewController: UIViewController { 4 | 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | // Do any additional setup after loading the view. 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-10.swift: -------------------------------------------------------------------------------- 1 | override func viewDidLoad() { 2 | super.viewDidLoad() 3 | // Do any additional setup after loading the view. 4 | print(KingfisherManager.shared) 5 | 6 | tableView.dataSource = self 7 | view.addSubview(tableView) 8 | NSLayoutConstraint.activate([ 9 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), 10 | tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), 11 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 12 | tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) 13 | ]) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-11.swift: -------------------------------------------------------------------------------- 1 | override func viewDidLoad() { 2 | super.viewDidLoad() 3 | // Do any additional setup after loading the view. 4 | print(KingfisherManager.shared) 5 | 6 | tableView.dataSource = self 7 | view.addSubview(tableView) 8 | NSLayoutConstraint.activate([ 9 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), 10 | tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), 11 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 12 | tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) 13 | ]) 14 | 15 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 16 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 17 | switch result { 18 | case .success(let size): 19 | print("Size: \(Double(size) / 1024 / 1024) MB") 20 | case .failure(let error): 21 | print("Some error: \(error)") 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-12.swift: -------------------------------------------------------------------------------- 1 | override func viewDidLoad() { 2 | super.viewDidLoad() 3 | // Do any additional setup after loading the view. 4 | print(KingfisherManager.shared) 5 | 6 | tableView.dataSource = self 7 | view.addSubview(tableView) 8 | NSLayoutConstraint.activate([ 9 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), 10 | tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), 11 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 12 | tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) 13 | ]) 14 | 15 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 16 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 17 | switch result { 18 | case .success(let size): 19 | let sizeInMB = Double(size) / 1024 / 1024 20 | let alert = UIAlertController(title: nil, message: String(format: "Kingfisher Disk Cache: %.2fMB", sizeInMB), preferredStyle: .alert) 21 | alert.addAction(UIAlertAction(title: "Purge", style: .destructive) { _ in 22 | 23 | }) 24 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) 25 | self.present(alert, animated: true) 26 | case .failure(let error): 27 | print("Some error: \(error)") 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-13.swift: -------------------------------------------------------------------------------- 1 | override func viewDidLoad() { 2 | super.viewDidLoad() 3 | // Do any additional setup after loading the view. 4 | print(KingfisherManager.shared) 5 | 6 | tableView.dataSource = self 7 | view.addSubview(tableView) 8 | NSLayoutConstraint.activate([ 9 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), 10 | tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), 11 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 12 | tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) 13 | ]) 14 | 15 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 16 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 17 | switch result { 18 | case .success(let size): 19 | let sizeInMB = Double(size) / 1024 / 1024 20 | let alert = UIAlertController(title: nil, message: String(format: "Kingfisher Disk Cache: %.2fMB", sizeInMB), preferredStyle: .alert) 21 | alert.addAction(UIAlertAction(title: "Purge", style: .destructive) { _ in 22 | KingfisherManager.shared.cache.clearCache { 23 | self.tableView.reloadData() 24 | } 25 | }) 26 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) 27 | self.present(alert, animated: true) 28 | case .failure(let error): 29 | print("Some error: \(error)") 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-2.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Kingfisher 3 | 4 | class ViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | // Do any additional setup after loading the view. 9 | print(KingfisherManager.shared) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-3.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Kingfisher 3 | 4 | class ViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | // Do any additional setup after loading the view. 9 | print(KingfisherManager.shared) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-4.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Kingfisher 3 | 4 | class ViewController: UIViewController { 5 | 6 | lazy var tableView: UITableView = { 7 | let tableView = UITableView(frame: .zero) 8 | tableView.register(SampleCell.self, forCellReuseIdentifier: "SampleCell") 9 | tableView.translatesAutoresizingMaskIntoConstraints = false 10 | tableView.rowHeight = 80 11 | return tableView 12 | }() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view. 17 | print(KingfisherManager.shared) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-5.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Kingfisher 3 | 4 | class ViewController: UIViewController { 5 | 6 | lazy var tableView: UITableView = { 7 | let tableView = UITableView(frame: .zero) 8 | tableView.register(SampleCell.self, forCellReuseIdentifier: "SampleCell") 9 | tableView.translatesAutoresizingMaskIntoConstraints = false 10 | tableView.rowHeight = 80 11 | return tableView 12 | }() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view. 17 | print(KingfisherManager.shared) 18 | 19 | tableView.dataSource = self 20 | view.addSubview(tableView) 21 | NSLayoutConstraint.activate([ 22 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), 23 | tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), 24 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 25 | tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) 26 | ]) 27 | } 28 | } 29 | 30 | extension ViewController: UITableViewDataSource { 31 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 32 | 1 33 | } 34 | 35 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 36 | let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell 37 | cell.sampleLabel.text = "Index \(indexPath.row)" 38 | cell.sampleImageView.backgroundColor = .lightGray 39 | return cell 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-6-0.swift: -------------------------------------------------------------------------------- 1 | extension ViewController: UITableViewDataSource { 2 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 3 | 1 4 | } 5 | 6 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 7 | let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell 8 | cell.sampleLabel.text = "Index \(indexPath.row)" 9 | cell.sampleImageView.backgroundColor = .lightGray 10 | return cell 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-6.swift: -------------------------------------------------------------------------------- 1 | extension ViewController: UITableViewDataSource { 2 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 3 | 1 4 | } 5 | 6 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 7 | let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell 8 | cell.sampleLabel.text = "Index \(indexPath.row)" 9 | 10 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 11 | let url = URL(string: "\(urlPrefix)-1.jpg") 12 | cell.sampleImageView.kf.setImage(with: url) 13 | 14 | cell.sampleImageView.backgroundColor = .lightGray 15 | return cell 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-7.swift: -------------------------------------------------------------------------------- 1 | extension ViewController: UITableViewDataSource { 2 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 3 | 10 4 | } 5 | 6 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 7 | let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell 8 | cell.sampleLabel.text = "Index \(indexPath.row)" 9 | 10 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 11 | let url = URL(string: "\(urlPrefix)-\(indexPath.row + 1).jpg") 12 | cell.sampleImageView.kf.setImage(with: url) 13 | 14 | cell.sampleImageView.backgroundColor = .lightGray 15 | return cell 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-8.swift: -------------------------------------------------------------------------------- 1 | extension ViewController: UITableViewDataSource { 2 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 3 | 10 4 | } 5 | 6 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 7 | let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell 8 | cell.sampleLabel.text = "Index \(indexPath.row)" 9 | 10 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 11 | let url = URL(string: "\(urlPrefix)-\(indexPath.row + 1).jpg") 12 | 13 | cell.sampleImageView.kf.indicatorType = .activity 14 | 15 | let roundCorner = RoundCornerImageProcessor(radius: .widthFraction(0.5), roundingCorners: [.topLeft, .bottomRight]) 16 | let pngSerializer = FormatIndicatedCacheSerializer.png 17 | cell.sampleImageView.kf.setImage( 18 | with: url, 19 | options: [.processor(roundCorner), .cacheSerializer(pngSerializer)] 20 | ) 21 | cell.sampleImageView.backgroundColor = .clear 22 | return cell 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/01-ViewController-9.swift: -------------------------------------------------------------------------------- 1 | // cell.sampleImageView.kf.setImage(with: url, options: [.processor(roundCorner)]) 2 | cell.sampleImageView.kf.setImage(with: url, options: [.processor(roundCorner)]) { result in 3 | switch result { 4 | case .success(let imageResult): 5 | print("Image loaded from cache: \(imageResult.cacheType)") 6 | case .failure(let error): 7 | print("Error: \(error)") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-1.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | var body: some View { 5 | VStack { 6 | Image(systemName: "globe") 7 | .imageScale(.large) 8 | .foregroundStyle(.tint) 9 | Text("Hello, world!") 10 | } 11 | .padding() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-10.swift: -------------------------------------------------------------------------------- 1 | @State var showAlert = false 2 | @State var cacheSizeResult: Result? = nil 3 | 4 | var body: some View { 5 | List { 6 | Button("Check Cache") { 7 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 8 | cacheSizeResult = result 9 | showAlert = true 10 | } 11 | } 12 | .alert( 13 | "Disk Cache", 14 | isPresented: $showAlert, 15 | presenting: cacheSizeResult, 16 | actions: { result in 17 | // TODO: Actions 18 | }, message: { result in 19 | switch result { 20 | case .success(let size): 21 | Text("Size: \(Double(size) / 1024 / 1024) MB") 22 | case .failure(let error): 23 | Text(error.localizedDescription) 24 | } 25 | }) 26 | 27 | ForEach(0 ..< 10) { i in 28 | HStack { 29 | KFImage(url(at: i)) 30 | // ... 31 | } 32 | } 33 | }.listStyle(.plain) 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-11.swift: -------------------------------------------------------------------------------- 1 | @State var showAlert = false 2 | @State var cacheSizeResult: Result? = nil 3 | 4 | var body: some View { 5 | List { 6 | Button("Check Cache") { 7 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 8 | cacheSizeResult = result 9 | showAlert = true 10 | } 11 | } 12 | .alert( 13 | "Disk Cache", 14 | isPresented: $showAlert, 15 | presenting: cacheSizeResult, 16 | actions: { result in 17 | switch result { 18 | case .success: 19 | Button("Clear") { 20 | KingfisherManager.shared.cache.clearCache() 21 | } 22 | Button("Cancel", role: .cancel) {} 23 | case .failure: 24 | Button("OK") { } 25 | } 26 | }, message: { result in 27 | switch result { 28 | case .success(let size): 29 | Text("Size: \(Double(size) / 1024 / 1024) MB") 30 | case .failure(let error): 31 | Text(error.localizedDescription) 32 | } 33 | }) 34 | 35 | ForEach(0 ..< 10) { i in 36 | HStack { 37 | KFImage(url(at: i)) 38 | // ... 39 | } 40 | } 41 | }.listStyle(.plain) 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-2.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Kingfisher 3 | 4 | struct ContentView: View { 5 | var body: some View { 6 | VStack { 7 | Image(systemName: "globe") 8 | .imageScale(.large) 9 | .foregroundStyle(.tint) 10 | Text("Hello, world!") 11 | } 12 | .padding() 13 | .onAppear { 14 | print(KingfisherManager.shared) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-3.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Kingfisher 3 | 4 | struct ContentView: View { 5 | var body: some View { 6 | List { 7 | ForEach(0 ..< 10) { i in 8 | HStack { 9 | Rectangle().fill(Color.gray) 10 | .frame(width: 64, height: 64) 11 | Text("Index \(i)") 12 | } 13 | } 14 | }.listStyle(.plain) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-4.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Kingfisher 3 | 4 | struct ContentView: View { 5 | func url(at index: Int) -> URL? { 6 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 7 | return URL(string: "\(urlPrefix)-\(index + 1).jpg") 8 | } 9 | 10 | var body: some View { 11 | List { 12 | ForEach(0 ..< 10) { i in 13 | HStack { 14 | KFImage(url(at: i)) 15 | .resizable() 16 | .frame(width: 64, height: 64) 17 | Text("Index \(i)") 18 | } 19 | } 20 | }.listStyle(.plain) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-5.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Kingfisher 3 | 4 | struct ContentView: View { 5 | func url(at index: Int) -> URL? { 6 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 7 | return URL(string: "\(urlPrefix)-\(index + 1).jpg") 8 | } 9 | 10 | var body: some View { 11 | List { 12 | ForEach(0 ..< 10) { i in 13 | HStack { 14 | KFImage(url(at: i)) 15 | .resizable() 16 | .roundCorner( 17 | radius: .widthFraction(0.5), 18 | roundingCorners: [.topLeft, .bottomRight] 19 | ) 20 | .serialize(as: .PNG) 21 | .frame(width: 64, height: 64) 22 | Text("Index \(i)") 23 | } 24 | } 25 | }.listStyle(.plain) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-6.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Kingfisher 3 | 4 | struct ContentView: View { 5 | func url(at index: Int) -> URL? { 6 | let urlPrefix = "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher" 7 | return URL(string: "\(urlPrefix)-\(index + 1).jpg") 8 | } 9 | 10 | var body: some View { 11 | List { 12 | ForEach(0 ..< 10) { i in 13 | HStack { 14 | KFImage(url(at: i)) 15 | .resizable() 16 | .roundCorner( 17 | radius: .widthFraction(0.5), 18 | roundingCorners: [.topLeft, .bottomRight] 19 | ) 20 | .serialize(as: .PNG) 21 | .onSuccess { result in 22 | print("Image loaded from cache: \(result.cacheType)") 23 | } 24 | .onFailure { error in 25 | print("Error: \(error)") 26 | } 27 | .frame(width: 64, height: 64) 28 | Text("Index \(i)") 29 | } 30 | } 31 | }.listStyle(.plain) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-7.swift: -------------------------------------------------------------------------------- 1 | var body: some View { 2 | List { 3 | ForEach(0 ..< 10) { i in 4 | HStack { 5 | KFImage(url(at: i)) 6 | // ... 7 | } 8 | } 9 | }.listStyle(.plain) 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-8.swift: -------------------------------------------------------------------------------- 1 | var body: some View { 2 | List { 3 | Button("Check Cache") { 4 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 5 | switch result { 6 | case .success(let size): 7 | print("Size: \(Double(size) / 1024 / 1024) MB") 8 | case .failure(let error): 9 | print("Some error: \(error)") 10 | } 11 | } 12 | } 13 | ForEach(0 ..< 10) { i in 14 | HStack { 15 | KFImage(url(at: i)) 16 | // ... 17 | } 18 | } 19 | }.listStyle(.plain) 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/code-files/02-ContentView-9.swift: -------------------------------------------------------------------------------- 1 | @State var showAlert = false 2 | @State var cacheSizeResult: Result? = nil 3 | 4 | var body: some View { 5 | List { 6 | Button("Check Cache") { 7 | KingfisherManager.shared.cache.calculateDiskStorageSize { result in 8 | cacheSizeResult = result 9 | showAlert = true 10 | } 11 | } 12 | ForEach(0 ..< 10) { i in 13 | HStack { 14 | KFImage(url(at: i)) 15 | // ... 16 | } 17 | } 18 | }.listStyle(.plain) 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/doc-art/common-tasks-card@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/doc-art/common-tasks-card@2x.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/doc-art/getting-started-card@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/doc-art/getting-started-card@2x.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/doc-art/imagedataprovider-sample@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/doc-art/imagedataprovider-sample@2x.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/doc-art/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/doc-art/logo@2x.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/add-dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/add-dependency.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/add-library-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/add-library-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/add-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/add-library.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/add-to-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/add-to-project.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/create-project-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/create-project-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/create-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/create-project.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-1-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-1-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-1.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-2-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-2-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-2.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-3-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-3-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-3.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-4-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-4-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-4.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-5-swiftui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-5-swiftui.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Resources/tutorial-art/preview-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Sources/Documentation.docc/Resources/tutorial-art/preview-5.png -------------------------------------------------------------------------------- /Sources/Documentation.docc/Topics/Topic_Indicator.md: -------------------------------------------------------------------------------- 1 | # Loading Indicator 2 | 3 | Setting and customizing indicator while loading. 4 | 5 | #### Using the standard indicator 6 | 7 | ```swift 8 | imageView.kf.indicatorType = .activity 9 | imageView.kf.setImage(with: url) 10 | ``` 11 | 12 | #### Using an image as indicator 13 | 14 | ```swift 15 | let path = Bundle.main.path(forResource: "loader", ofType: "gif")! 16 | let data = try! Data(contentsOf: URL(fileURLWithPath: path)) 17 | 18 | imageView.kf.indicatorType = .image(imageData: data) 19 | imageView.kf.setImage(with: url) 20 | ``` 21 | 22 | #### Using a customized view 23 | 24 | ```swift 25 | struct MyIndicator: Indicator { 26 | let view: UIView = UIView() 27 | 28 | func startAnimatingView() { view.isHidden = false } 29 | func stopAnimatingView() { view.isHidden = true } 30 | 31 | init() { 32 | view.backgroundColor = .red 33 | } 34 | } 35 | 36 | let i = MyIndicator() 37 | imageView.kf.indicatorType = .custom(indicator: i) 38 | ``` 39 | 40 | #### Updating indicator with percentage progress 41 | 42 | ```swift 43 | imageView.kf.setImage(with: url, progressBlock: { 44 | receivedSize, totalSize in 45 | let percentage = (Float(receivedSize) / Float(totalSize)) * 100.0 46 | print("downloading progress: \(percentage)%") 47 | myIndicator.percentage = percentage 48 | }) 49 | ``` 50 | 51 | The `progressBlock` is called only when the server's response includes a "Content-Length" in the header. 52 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Topics/Topic_LowDataMode.md: -------------------------------------------------------------------------------- 1 | # Low Data Mode 2 | 3 | Loading image and customizing behaviors for the Low Data Mode. 4 | 5 | ## Overview 6 | 7 | Starting with iOS 13, Apple has introduced the option for users to enable 8 | [Low Data Mode](https://support.apple.com/en-us/102433) to reduce cellular and Wi-Fi data usage. To accommodate this 9 | setting, you can offer an alternative version of your image, typically in lower resolution. Kingfisher will 10 | automatically switch to this version when Low Data Mode is activated, helping to conserve data. 11 | 12 | ```swift 13 | imageView.kf.setImage( 14 | with: highResolutionURL, 15 | options: [.lowDataSource(.network(lowResolutionURL)] 16 | ) 17 | ``` 18 | 19 | In the scenario described, if the user has not applied any network restrictions, the `highResolutionURL` will be 20 | utilized for fetching the image. However, if the device is in Low Data Mode and the `highResolutionURL` version is not 21 | found in the cache, the `lowResolutionURL` will be selected as the fallback option to save data. 22 | 23 | Given that the `.lowDataSource` option accepts any `Source` parameter, not just a URL, you have the flexibility to pass 24 | in a local image provider. This approach effectively eliminates the need for a downloading task, allowing for the use 25 | of locally stored images when operating under Low Data Mode or other restrictive network conditions. 26 | 27 | ```swift 28 | imageView.kf.setImage( 29 | with: highResolutionURL, 30 | options: [ 31 | .lowDataSource( 32 | .provider(LocalFileImageDataProvider(fileURL: localFileURL)) 33 | ) 34 | ] 35 | ) 36 | ``` 37 | 38 | > For more about this topic, check and ``ImageDataProvider`` documentation. 39 | 40 | > tip: If the `.lowDataSource` option is not specified, the `highResolutionURL` will be used by default, regardless of 41 | > the Low Data Mode setting on the device. 42 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Topics/Topic_Prefetch.md: -------------------------------------------------------------------------------- 1 | # Prefetching images before actually loading 2 | 3 | Preloading images before actually required. Feeding them to the table view or collection view to improve the display speed. 4 | 5 | ## Overview 6 | 7 | Use ``ImagePrefetcher`` to prefetch and cache images that are likely to be displayed later. This improves loading times 8 | and ensures smoother image display. 9 | 10 | ### Prefetch some images 11 | 12 | ```swift 13 | let urls = [ 14 | "https://example.com/image1.jpg", 15 | "https://example.com/image2.jpg" 16 | ].map { URL(string: $0)! } 17 | 18 | let prefetcher = ImagePrefetcher(urls: urls) { 19 | skippedResources, failedResources, completedResources in 20 | print("These resources are prefetched: \(completedResources)") 21 | } 22 | prefetcher.start() 23 | 24 | // Later when you need to display these images: 25 | imageView.kf.setImage(with: urls[0]) 26 | anotherImageView.kf.setImage(with: urls[1]) 27 | ``` 28 | 29 | ### Prefetch images for table view or collection view 30 | 31 | Starting with iOS 10, Apple introduced cell prefetching behavior, which can seamlessly integrate with Kingfisher's 32 | ``ImagePrefetcher``. 33 | 34 | ```swift 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | collectionView?.prefetchDataSource = self 38 | } 39 | 40 | extension ViewController: UICollectionViewDataSourcePrefetching { 41 | func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { 42 | let urls = indexPaths.flatMap { URL(string: $0.urlString) } 43 | ImagePrefetcher(urls: urls).start() 44 | } 45 | } 46 | ``` 47 | 48 | See [WWDC 16 - Session 219](https://developer.apple.com/videos/play/wwdc2016/219/) for more about changing of it in iOS 10. 49 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Topics/Topic_Retry.md: -------------------------------------------------------------------------------- 1 | # Retry the Image Loading 2 | 3 | Managing the retry mechanism when an error happens during loading. 4 | 5 | ## Overview 6 | 7 | Use ``KingfisherOptionsInfoItem/retryStrategy(_:)`` along with a `RetryStrategy` implementation to easily set up a 8 | retry mechanism for image setting operations when an error occurs. 9 | 10 | This combination allows you to define retry logic, including the number of retries and the conditions under which a 11 | retry should be attempted, ensuring a more resilient image loading process. 12 | 13 | 14 | ## Basic Retry Strategy 15 | 16 | ``DelayRetryStrategy`` is a predefined retry strategy in Kingfisher. It allows you to specify the `maxRetryCount` and 17 | the `retryInterval` to easily configure retry behavior. This setup enables quick implementation of a retry mechanism: 18 | 19 | ```swift 20 | let retry = DelayRetryStrategy( 21 | maxRetryCount: 5, 22 | retryInterval: .seconds(3) 23 | ) 24 | imageView.kf.setImage(with: url, options: [.retryStrategy(retry)]) 25 | ``` 26 | 27 | This implements a retry mechanism that attempts to reload the target URL up to 5 times, with a fixed 3-second interval 28 | between each try. 29 | 30 | #### Other retry interval 31 | 32 | For a more dynamic approach, you can also select `.accumulated(3)` as the retry interval results in progressively 33 | increasing delays between attempts, specifically `3 -> 6 -> 9 -> 12 -> 15` seconds for each subsequent retry. 34 | Additionally, for ultimate flexibility, `.custom` allows you to define a unique pattern for retry intervals, tailoring 35 | the retry logic to your specific requirements. 36 | 37 | If you need more control for the retry strategy, implement your own type that conforms to ``RetryStrategy``. 38 | -------------------------------------------------------------------------------- /Sources/Documentation.docc/Tutorials/Tutorials.tutorial: -------------------------------------------------------------------------------- 1 | @Tutorials(name: "Kingfisher Tutorials") { 2 | @Intro(title: "Kingfisher Tutorials") { 3 | Getting started with Kingfisher by following a sample app. 4 | } 5 | 6 | @Chapter(name: "Getting Started with Kingfisher (UIKit)") { 7 | @Image(source: logo) 8 | Installs Kingfisher and basic usage of the framework with UIKit. 9 | @TutorialReference(tutorial: "doc:GettingStartedUIKit") 10 | } 11 | 12 | @Chapter(name: "Getting Started with Kingfisher (SwiftUI)") { 13 | @Image(source: logo) 14 | Installs Kingfisher and basic usage of the framework with SwiftUI. 15 | @TutorialReference(tutorial: "doc:GettingStartedSwiftUI") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 8.3.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 3097 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyTracking 17 | 18 | NSPrivacyTrackingDomains 19 | 20 | 21 | NSPrivacyCollectedDataTypes 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/Utility/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Box.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2018/3/17. 6 | // Copyright (c) 2019 Wei Wang 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | class Box { 29 | var value: T 30 | 31 | init(_ value: T) { 32 | self.value = value 33 | } 34 | } 35 | 36 | actor ActorBox { 37 | var value: T 38 | init(_ value: T) { 39 | self.value = value 40 | } 41 | 42 | func setValue(_ value: T) { 43 | self.value = value 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Utility/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/09/22. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | // These helper methods are not public since we do not want them to be exposed or cause any conflicting. 30 | // However, they are just wrapper of `ResultUtil` static methods. 31 | extension Result where Failure: Error { 32 | 33 | /// Evaluates the given transformation closures to create a single output value. 34 | /// 35 | /// - Parameters: 36 | /// - onSuccess: A closure that transforms the success value. 37 | /// - onFailure: A closure that transforms the error value. 38 | /// - Returns: A single `Output` value. 39 | func match( 40 | onSuccess: (Success) -> Output, 41 | onFailure: (Failure) -> Output) -> Output 42 | { 43 | switch self { 44 | case let .success(value): 45 | return onSuccess(value) 46 | case let .failure(error): 47 | return onFailure(error) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Utility/Runtime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Runtime.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2018/10/12. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | func getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> T? { 30 | if #available(iOS 14, macOS 11, watchOS 7, tvOS 14, *) { // swift 5.3 fixed this issue (https://github.com/swiftlang/swift/issues/46456) 31 | return objc_getAssociatedObject(object, key) as? T 32 | } else { 33 | return objc_getAssociatedObject(object, key) as AnyObject as? T 34 | } 35 | } 36 | 37 | func setRetainedAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: T) { 38 | objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Utility/String+SHA256.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+SHA256.swift 3 | // Kingfisher 4 | // 5 | // Created by kaimaschke on 28.07.23. 6 | // 7 | // Copyright (c) 2023 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | import CryptoKit 29 | import CommonCrypto 30 | 31 | extension String: KingfisherCompatibleValue { } 32 | extension KingfisherWrapper where Base == String { 33 | var sha256: String { 34 | guard let data = base.data(using: .utf8) else { return base } 35 | if #available(iOS 13.0, tvOS 13.0, macOS 10.15, watchOS 6.0, macCatalyst 13.0, *) { 36 | let hashed = SHA256.hash(data: data) 37 | return hashed.compactMap { String(format: "%02x", $0) }.joined() 38 | } else { 39 | var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 40 | data.withUnsafeBytes { bytes in 41 | _ = CC_SHA256(bytes.baseAddress, UInt32(data.count), &digest) 42 | } 43 | return digest.makeIterator().compactMap { String(format: "%02x", $0) }.joined() 44 | } 45 | } 46 | 47 | var ext: String? { 48 | guard let firstSeg = base.split(separator: "@").first else { 49 | return nil 50 | } 51 | 52 | var ext = "" 53 | if let index = firstSeg.lastIndex(of: ".") { 54 | let extRange = firstSeg.index(index, offsetBy: 1).. 0 ? ext : nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Luis Solano Bonet 2 | MIT License 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Categories/NSData+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSData (Nocilla) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Categories/NSData+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSData+Nocilla.h" 2 | 3 | @implementation NSData (Nocilla) 4 | 5 | - (NSData *)data { 6 | return self; 7 | } 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Categories/NSString+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSString (Nocilla) 5 | 6 | - (NSRegularExpression *)regex; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Categories/NSString+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Nocilla.h" 2 | 3 | @implementation NSString (Nocilla) 4 | 5 | - (NSRegularExpression *)regex { 6 | NSError *error = nil; 7 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:self options:0 error:&error]; 8 | if (error) { 9 | [NSException raise:NSInvalidArgumentException format:@"Invalid regex pattern: %@\nError: %@", self, error]; 10 | } 11 | return regex; 12 | } 13 | 14 | - (NSData *)data { 15 | return [self dataUsingEncoding:NSUTF8StringEncoding]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSHTTPRequestDSLRepresentation.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDSLRepresentation : NSObject 5 | - (id)initWithRequest:(id)request; 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSHTTPRequestDSLRepresentation.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPRequestDSLRepresentation.h" 2 | 3 | @interface LSHTTPRequestDSLRepresentation () 4 | @property (nonatomic, strong) id request; 5 | @end 6 | 7 | @implementation LSHTTPRequestDSLRepresentation 8 | - (id)initWithRequest:(id)request { 9 | self = [super init]; 10 | if (self) { 11 | _request = request; 12 | } 13 | return self; 14 | } 15 | 16 | - (NSString *)description { 17 | NSMutableString *result = [NSMutableString stringWithFormat:@"stubRequest(@\"%@\", @\"%@\")", self.request.method, [self.request.url absoluteString]]; 18 | if (self.request.headers.count) { 19 | [result appendString:@".\nwithHeaders(@{ "]; 20 | NSMutableArray *headerElements = [NSMutableArray arrayWithCapacity:self.request.headers.count]; 21 | 22 | NSArray *descriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES]]; 23 | NSArray * sortedHeaders = [[self.request.headers allKeys] sortedArrayUsingDescriptors:descriptors]; 24 | 25 | for (NSString * header in sortedHeaders) { 26 | NSString *value = [self.request.headers objectForKey:header]; 27 | [headerElements addObject:[NSString stringWithFormat:@"@\"%@\": @\"%@\"", header, value]]; 28 | } 29 | [result appendString:[headerElements componentsJoinedByString:@", "]]; 30 | [result appendString:@" })"]; 31 | } 32 | if (self.request.body.length) { 33 | NSString *escapedBody = [[NSString alloc] initWithData:self.request.body encoding:NSUTF8StringEncoding]; 34 | escapedBody = [escapedBody stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 35 | [result appendFormat:@".\nwithBody(@\"%@\")", escapedBody]; 36 | } 37 | return [NSString stringWithFormat:@"%@;", result]; 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSStubRequestDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSString+Matcheable.h" 3 | #import "NSRegularExpression+Matcheable.h" 4 | #import "NSData+Matcheable.h" 5 | 6 | @class LSStubRequestDSL; 7 | @class LSStubResponseDSL; 8 | @class LSStubRequest; 9 | 10 | @protocol LSHTTPBody; 11 | 12 | typedef LSStubRequestDSL *(^WithHeaderMethod)(NSString *, NSString *); 13 | typedef LSStubRequestDSL *(^WithHeadersMethod)(NSDictionary *); 14 | typedef LSStubRequestDSL *(^AndBodyMethod)(id); 15 | typedef LSStubResponseDSL *(^AndReturnMethod)(NSInteger); 16 | typedef LSStubResponseDSL *(^AndReturnRawResponseMethod)(NSData *rawResponseData); 17 | typedef void (^AndFailWithErrorMethod)(NSError *error); 18 | 19 | @interface LSStubRequestDSL : NSObject 20 | - (id)initWithRequest:(LSStubRequest *)request; 21 | 22 | @property (nonatomic, strong, readonly) WithHeaderMethod withHeader; 23 | @property (nonatomic, strong, readonly) WithHeadersMethod withHeaders; 24 | @property (nonatomic, strong, readonly) AndBodyMethod withBody; 25 | @property (nonatomic, strong, readonly) AndReturnMethod andReturn; 26 | @property (nonatomic, strong, readonly) AndReturnRawResponseMethod andReturnRawResponse; 27 | @property (nonatomic, strong, readonly) AndFailWithErrorMethod andFailWithError; 28 | 29 | @end 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | LSStubRequestDSL * stubRequest(NSString *method, id url); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSStubRequestDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubRequestDSL.h" 2 | #import "LSStubResponseDSL.h" 3 | #import "LSStubRequest.h" 4 | #import "LSNocilla.h" 5 | 6 | @interface LSStubRequestDSL () 7 | @property (nonatomic, strong) LSStubRequest *request; 8 | @end 9 | 10 | @implementation LSStubRequestDSL 11 | 12 | - (id)initWithRequest:(LSStubRequest *)request { 13 | self = [super init]; 14 | if (self) { 15 | _request = request; 16 | } 17 | return self; 18 | } 19 | - (WithHeadersMethod)withHeaders { 20 | return ^(NSDictionary *headers) { 21 | for (NSString *header in headers) { 22 | NSString *value = [headers objectForKey:header]; 23 | [self.request setHeader:header value:value]; 24 | } 25 | return self; 26 | }; 27 | } 28 | 29 | - (WithHeaderMethod)withHeader { 30 | return ^(NSString * header, NSString * value) { 31 | [self.request setHeader:header value:value]; 32 | return self; 33 | }; 34 | } 35 | 36 | - (AndBodyMethod)withBody { 37 | return ^(id body) { 38 | self.request.body = body.matcher; 39 | return self; 40 | }; 41 | } 42 | 43 | - (AndReturnMethod)andReturn { 44 | return ^(NSInteger statusCode) { 45 | self.request.response = [[LSStubResponse alloc] initWithStatusCode:statusCode]; 46 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 47 | return responseDSL; 48 | }; 49 | } 50 | 51 | - (AndReturnRawResponseMethod)andReturnRawResponse { 52 | return ^(NSData *rawResponseData) { 53 | self.request.response = [[LSStubResponse alloc] initWithRawResponse:rawResponseData]; 54 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 55 | return responseDSL; 56 | }; 57 | } 58 | 59 | - (AndFailWithErrorMethod)andFailWithError { 60 | return ^(NSError *error) { 61 | self.request.response = [[LSStubResponse alloc] initWithError:error]; 62 | }; 63 | } 64 | 65 | @end 66 | 67 | LSStubRequestDSL * stubRequest(NSString *method, id url) { 68 | LSStubRequest *request = [[LSStubRequest alloc] initWithMethod:method urlMatcher:url.matcher]; 69 | LSStubRequestDSL *dsl = [[LSStubRequestDSL alloc] initWithRequest:request]; 70 | [[LSNocilla sharedInstance] addStubbedRequest:request]; 71 | return dsl; 72 | } 73 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSStubResponseDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSStubResponse; 4 | @class LSStubResponseDSL; 5 | 6 | @protocol LSHTTPBody; 7 | 8 | typedef LSStubResponseDSL *(^ResponseWithBodyMethod)(id); 9 | typedef LSStubResponseDSL *(^ResponseWithHeaderMethod)(NSString *, NSString *); 10 | typedef LSStubResponseDSL *(^ResponseWithHeadersMethod)(NSDictionary *); 11 | typedef LSStubResponseDSL *(^ResponseVoidMethod)(void); 12 | 13 | @interface LSStubResponseDSL : NSObject 14 | - (id)initWithResponse:(LSStubResponse *)response; 15 | 16 | @property (nonatomic, strong, readonly) ResponseWithHeaderMethod withHeader; 17 | @property (nonatomic, strong, readonly) ResponseWithHeadersMethod withHeaders; 18 | @property (nonatomic, strong, readonly) ResponseWithBodyMethod withBody; 19 | 20 | @property (nonatomic, strong, readonly) ResponseVoidMethod delay; 21 | @property (nonatomic, strong, readonly) ResponseVoidMethod go; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/DSL/LSStubResponseDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubResponseDSL.h" 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPBody.h" 4 | 5 | @interface LSStubResponseDSL () 6 | @property (nonatomic, strong) LSStubResponse *response; 7 | @end 8 | 9 | @implementation LSStubResponseDSL 10 | - (id)initWithResponse:(LSStubResponse *)response { 11 | self = [super init]; 12 | if (self) { 13 | _response = response; 14 | } 15 | return self; 16 | } 17 | - (ResponseWithHeaderMethod)withHeader { 18 | return ^(NSString * header, NSString * value) { 19 | [self.response setHeader:header value:value]; 20 | return self; 21 | }; 22 | } 23 | 24 | - (ResponseWithHeadersMethod)withHeaders; { 25 | return ^(NSDictionary *headers) { 26 | for (NSString *header in headers) { 27 | NSString *value = [headers objectForKey:header]; 28 | [self.response setHeader:header value:value]; 29 | } 30 | return self; 31 | }; 32 | } 33 | 34 | - (ResponseWithBodyMethod)withBody { 35 | return ^(id body) { 36 | self.response.body = [body data]; 37 | return self; 38 | }; 39 | } 40 | 41 | - (ResponseVoidMethod)delay { 42 | return ^{ 43 | [self.response delay]; 44 | return self; 45 | }; 46 | } 47 | 48 | - (ResponseVoidMethod)go { 49 | return ^{ 50 | [self.response go]; 51 | return self; 52 | }; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Diff/LSHTTPRequestDiff.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDiff : NSObject 5 | @property (nonatomic, assign, readonly, getter = isEmpty) BOOL empty; 6 | 7 | - (id)initWithRequest:(id)oneRequest andRequest:(id)anotherRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ASIHTTPRequestStub : NSObject 4 | - (int)stub_responseStatusCode; 5 | - (NSData *)stub_responseData; 6 | - (NSDictionary *)stub_responseHeaders; 7 | - (void)stub_startRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.m: -------------------------------------------------------------------------------- 1 | #import "ASIHTTPRequestStub.h" 2 | #import "LSStubResponse.h" 3 | #import "LSNocilla.h" 4 | #import "LSASIHTTPRequestAdapter.h" 5 | #import 6 | 7 | @interface ASIHTTPRequestStub () 8 | @property (nonatomic, strong) LSStubResponse *stubResponse; 9 | @end 10 | 11 | @interface ASIHTTPRequestStub (Private) 12 | - (void)failWithError:(NSError *)error; 13 | - (void)requestFinished; 14 | - (void)markAsFinished; 15 | @end 16 | 17 | static void const * ASIHTTPRequestStubResponseKey = &ASIHTTPRequestStubResponseKey; 18 | 19 | @implementation ASIHTTPRequestStub 20 | 21 | - (void)setStubResponse:(LSStubResponse *)stubResponse { 22 | objc_setAssociatedObject(self, ASIHTTPRequestStubResponseKey, stubResponse, OBJC_ASSOCIATION_RETAIN); 23 | } 24 | 25 | - (LSStubResponse *)stubResponse { 26 | return objc_getAssociatedObject(self, ASIHTTPRequestStubResponseKey); 27 | } 28 | 29 | - (int)stub_responseStatusCode { 30 | return (int)self.stubResponse.statusCode; 31 | } 32 | 33 | - (NSData *)stub_responseData { 34 | return self.stubResponse.body; 35 | } 36 | 37 | - (NSDictionary *)stub_responseHeaders { 38 | return self.stubResponse.headers; 39 | } 40 | 41 | - (void)stub_startRequest { 42 | self.stubResponse = [[LSNocilla sharedInstance] responseForRequest:[[LSASIHTTPRequestAdapter alloc] initWithASIHTTPRequest:(id)self]]; 43 | 44 | if (self.stubResponse.shouldFail) { 45 | [self failWithError:self.stubResponse.error]; 46 | } else { 47 | [self requestFinished]; 48 | } 49 | [self markAsFinished]; 50 | } 51 | 52 | @end -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @class ASIHTTPRequest; 5 | 6 | @interface LSASIHTTPRequestAdapter : NSObject 7 | 8 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestAdapter.h" 2 | 3 | @interface ASIHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *requestMethod; 7 | @property (nonatomic, strong, readonly) NSDictionary *requestHeaders; 8 | @property (nonatomic, strong, readonly) NSData *postBody; 9 | 10 | @end 11 | 12 | @interface LSASIHTTPRequestAdapter () 13 | @property (nonatomic, strong) ASIHTTPRequest *request; 14 | @end 15 | 16 | @implementation LSASIHTTPRequestAdapter 17 | 18 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request { 19 | self = [super init]; 20 | if (self) { 21 | _request = request; 22 | } 23 | return self; 24 | } 25 | 26 | - (NSURL *)url { 27 | return self.request.url; 28 | } 29 | 30 | - (NSString *)method { 31 | return self.request.requestMethod; 32 | } 33 | 34 | - (NSDictionary *)headers { 35 | return self.request.requestHeaders; 36 | } 37 | 38 | - (NSData *)body { 39 | return self.request.postBody; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSASIHTTPRequestHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestHook.h" 2 | #import "ASIHTTPRequestStub.h" 3 | #import 4 | 5 | @implementation LSASIHTTPRequestHook 6 | 7 | - (void)load { 8 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 9 | [self swizzleASIHTTPRequest]; 10 | } 11 | 12 | - (void)unload { 13 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 14 | [self swizzleASIHTTPRequest]; 15 | } 16 | 17 | #pragma mark - Internal Methods 18 | 19 | - (void)swizzleASIHTTPRequest { 20 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseStatusCode") withSelector:@selector(stub_responseStatusCode)]; 21 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseData") withSelector:@selector(stub_responseData)]; 22 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseHeaders") withSelector:@selector(stub_responseHeaders)]; 23 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"startRequest") withSelector:@selector(stub_startRequest)]; 24 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"stubResponse")]; 25 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"setStubResponse:")]; 26 | } 27 | 28 | - (void)swizzleASIHTTPSelector:(SEL)original withSelector:(SEL)stub { 29 | Class asiHttpRequest = NSClassFromString(@"ASIHTTPRequest"); 30 | Method originalMethod = class_getInstanceMethod(asiHttpRequest, original); 31 | Method stubMethod = class_getInstanceMethod([ASIHTTPRequestStub class], stub); 32 | if (!originalMethod || !stubMethod) { 33 | [self fail]; 34 | } 35 | method_exchangeImplementations(originalMethod, stubMethod); 36 | } 37 | 38 | - (void)addMethodToASIHTTPRequest:(SEL)newMethod { 39 | Method method = class_getInstanceMethod([ASIHTTPRequestStub class], newMethod); 40 | const char *types = method_getTypeEncoding(method); 41 | class_addMethod(NSClassFromString(@"ASIHTTPRequest"), newMethod, class_getMethodImplementation([ASIHTTPRequestStub class], newMethod), types); 42 | } 43 | 44 | - (void)fail { 45 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load ASIHTTPRequest hook."]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/LSHTTPClientHook.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPClientHook : NSObject 4 | - (void)load; 5 | - (void)unload; 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/LSHTTPClientHook.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @implementation LSHTTPClientHook 4 | - (void)load { 5 | [NSException raise:NSInternalInconsistencyException 6 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 7 | } 8 | 9 | - (void)unload { 10 | [NSException raise:NSInternalInconsistencyException 11 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 12 | } 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPStubURLProtocol : NSURLProtocol 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSNSURLHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m: -------------------------------------------------------------------------------- 1 | #import "LSNSURLHook.h" 2 | #import "LSHTTPStubURLProtocol.h" 3 | 4 | @implementation LSNSURLHook 5 | 6 | - (void)load { 7 | [NSURLProtocol registerClass:[LSHTTPStubURLProtocol class]]; 8 | } 9 | 10 | - (void)unload { 11 | [NSURLProtocol unregisterClass:[LSHTTPStubURLProtocol class]]; 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSURLRequest (DSL) 4 | - (NSString *)toNocillaDSL; 5 | @end 6 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+DSL.h" 2 | #import "LSHTTPRequestDSLRepresentation.h" 3 | #import "NSURLRequest+LSHTTPRequest.h" 4 | 5 | @implementation NSURLRequest (DSL) 6 | - (NSString *)toNocillaDSL { 7 | return [[[LSHTTPRequestDSLRepresentation alloc] initWithRequest:self] description]; 8 | } 9 | @end 10 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface NSURLRequest (LSHTTPRequest) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+LSHTTPRequest.h" 2 | 3 | @implementation NSURLRequest (LSHTTPRequest) 4 | 5 | - (NSURL*)url { 6 | return self.URL; 7 | } 8 | 9 | - (NSString *)method { 10 | return self.HTTPMethod; 11 | } 12 | 13 | - (NSDictionary *)headers { 14 | return self.allHTTPHeaderFields; 15 | } 16 | 17 | - (NSData *)body { 18 | if (self.HTTPBodyStream) { 19 | NSInputStream *stream = self.HTTPBodyStream; 20 | NSMutableData *data = [NSMutableData data]; 21 | [stream open]; 22 | size_t bufferSize = 4096; 23 | uint8_t *buffer = malloc(bufferSize); 24 | if (buffer == NULL) { 25 | [NSException raise:@"NocillaMallocFailure" format:@"Could not allocate %zu bytes to read HTTPBodyStream", bufferSize]; 26 | } 27 | while ([stream hasBytesAvailable]) { 28 | NSInteger bytesRead = [stream read:buffer maxLength:bufferSize]; 29 | if (bytesRead > 0) { 30 | NSData *readData = [NSData dataWithBytes:buffer length:bytesRead]; 31 | [data appendData:readData]; 32 | } else if (bytesRead < 0) { 33 | [NSException raise:@"NocillaStreamReadError" format:@"An error occurred while reading HTTPBodyStream (%ld)", (long)bytesRead]; 34 | } else if (bytesRead == 0) { 35 | break; 36 | } 37 | } 38 | free(buffer); 39 | [stream close]; 40 | 41 | return data; 42 | } 43 | 44 | return self.HTTPBody; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "Nocilla.h" 10 | 11 | #import "LSHTTPClientHook.h" 12 | 13 | @interface LSNSURLSessionHook : LSHTTPClientHook 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSNSURLSessionHook.h" 10 | #import "LSHTTPStubURLProtocol.h" 11 | #import 12 | 13 | @implementation LSNSURLSessionHook 14 | 15 | - (void)load { 16 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 17 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 18 | } 19 | 20 | - (void)unload { 21 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 22 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 23 | } 24 | 25 | - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub { 26 | 27 | Method originalMethod = class_getInstanceMethod(original, selector); 28 | Method stubMethod = class_getInstanceMethod(stub, selector); 29 | if (!originalMethod || !stubMethod) { 30 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load NSURLSession hook."]; 31 | } 32 | method_exchangeImplementations(originalMethod, stubMethod); 33 | } 34 | 35 | - (NSArray *)protocolClasses { 36 | return @[[LSHTTPStubURLProtocol class]]; 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/LSNocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Nocilla.h" 3 | 4 | @class LSStubRequest; 5 | @class LSStubResponse; 6 | @class LSHTTPClientHook; 7 | @protocol LSHTTPRequest; 8 | 9 | extern NSString * const LSUnexpectedRequest; 10 | 11 | @interface LSNocilla : NSObject 12 | + (LSNocilla *)sharedInstance; 13 | 14 | @property (nonatomic, strong, readonly) NSArray *stubbedRequests; 15 | @property (nonatomic, assign, readonly, getter = isStarted) BOOL started; 16 | 17 | - (void)start; 18 | - (void)stop; 19 | - (void)addStubbedRequest:(LSStubRequest *)request; 20 | - (void)clearStubs; 21 | 22 | - (void)registerHook:(LSHTTPClientHook *)hook; 23 | 24 | - (LSStubResponse *)responseForRequest:(id)request; 25 | @end 26 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSDataMatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcher.h" 11 | 12 | @interface LSDataMatcher : LSMatcher 13 | 14 | - (instancetype)initWithData:(NSData *)data; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSDataMatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSDataMatcher.h" 10 | 11 | @interface LSDataMatcher () 12 | 13 | @property (nonatomic, copy) NSData *data; 14 | 15 | @end 16 | 17 | @implementation LSDataMatcher 18 | 19 | - (instancetype)initWithData:(NSData *)data { 20 | self = [super init]; 21 | 22 | if (self) { 23 | _data = data; 24 | } 25 | return self; 26 | } 27 | 28 | - (BOOL)matchesData:(NSData *)data { 29 | return [self.data isEqualToData:data]; 30 | } 31 | 32 | 33 | #pragma mark - Equality 34 | 35 | - (BOOL)isEqual:(id)object { 36 | if (self == object) { 37 | return YES; 38 | } 39 | 40 | if (![object isKindOfClass:[LSDataMatcher class]]) { 41 | return NO; 42 | } 43 | 44 | return [self.data isEqual:((LSDataMatcher *)object).data]; 45 | } 46 | 47 | - (NSUInteger)hash { 48 | return self.data.hash; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSMatcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSMatcher; 4 | 5 | @protocol LSMatcheable 6 | 7 | - (LSMatcher *)matcher; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSMatcher : NSObject 4 | 5 | - (BOOL)matches:(NSString *)string; 6 | 7 | - (BOOL)matchesData:(NSData *)data; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @implementation LSMatcher 4 | 5 | - (BOOL)matches:(NSString *)string { 6 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher matches:] is an abstract method" userInfo:nil]; 7 | } 8 | 9 | - (BOOL)matchesData:(NSData *)data { 10 | return [self matches:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; 11 | } 12 | 13 | 14 | #pragma mark - Equality 15 | 16 | - (BOOL)isEqual:(id)object { 17 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher isEqual:] is an abstract method" userInfo:nil]; 18 | } 19 | 20 | - (NSUInteger)hash { 21 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher hash] an abstract method" userInfo:nil]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSRegexMatcher.h: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @interface LSRegexMatcher : LSMatcher 4 | 5 | - (instancetype)initWithRegex:(NSRegularExpression *)regex; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSRegexMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSRegexMatcher.h" 2 | 3 | @interface LSRegexMatcher () 4 | @property (nonatomic, strong) NSRegularExpression *regex; 5 | @end 6 | 7 | @implementation LSRegexMatcher 8 | 9 | - (instancetype)initWithRegex:(NSRegularExpression *)regex { 10 | self = [super init]; 11 | if (self) { 12 | _regex = regex; 13 | } 14 | return self; 15 | } 16 | 17 | - (BOOL)matches:(NSString *)string { 18 | return [self.regex numberOfMatchesInString:string options:0 range:NSMakeRange(0, string.length)] > 0; 19 | } 20 | 21 | 22 | #pragma mark - Equality 23 | 24 | - (BOOL)isEqual:(id)object { 25 | if (self == object) { 26 | return YES; 27 | } 28 | 29 | if (![object isKindOfClass:[LSRegexMatcher class]]) { 30 | return NO; 31 | } 32 | 33 | return [self.regex isEqual:((LSRegexMatcher *)object).regex]; 34 | } 35 | 36 | - (NSUInteger)hash { 37 | return self.regex.hash; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSStringMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcher.h" 3 | 4 | @interface LSStringMatcher : LSMatcher 5 | 6 | - (instancetype)initWithString:(NSString *)string; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/LSStringMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSStringMatcher.h" 2 | 3 | @interface LSStringMatcher () 4 | 5 | @property (nonatomic, copy) NSString *string; 6 | 7 | @end 8 | 9 | @implementation LSStringMatcher 10 | 11 | - (instancetype)initWithString:(NSString *)string { 12 | self = [super init]; 13 | if (self) { 14 | _string = string; 15 | } 16 | return self; 17 | } 18 | 19 | - (BOOL)matches:(NSString *)string { 20 | return [self.string isEqualToString:string]; 21 | } 22 | 23 | 24 | #pragma mark - Equality 25 | 26 | - (BOOL)isEqual:(id)object { 27 | if (self == object) { 28 | return YES; 29 | } 30 | 31 | if (![object isKindOfClass:[LSStringMatcher class]]) { 32 | return NO; 33 | } 34 | 35 | return [self.string isEqualToString:((LSStringMatcher *)object).string]; 36 | } 37 | 38 | - (NSUInteger)hash { 39 | return self.string.hash; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSData+Matcheable.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcheable.h" 11 | 12 | @interface NSData (Matcheable) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSData+Matcheable.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "NSData+Matcheable.h" 10 | #import "LSDataMatcher.h" 11 | 12 | @implementation NSData (Matcheable) 13 | 14 | - (LSMatcher *)matcher { 15 | return [[LSDataMatcher alloc] initWithData:self]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSRegularExpression+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSRegularExpression (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSRegularExpression+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSRegularExpression+Matcheable.h" 2 | #import "LSRegexMatcher.h" 3 | 4 | @implementation NSRegularExpression (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSRegexMatcher alloc] initWithRegex:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSString+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSString (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Matchers/NSString+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Matcheable.h" 2 | #import "LSStringMatcher.h" 3 | 4 | @implementation NSString (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSStringMatcher alloc] initWithString:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Model/LSHTTPBody.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPBody 4 | - (NSData *)data; 5 | @end 6 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Model/LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *method; 7 | @property (nonatomic, strong, readonly) NSDictionary *headers; 8 | @property (nonatomic, strong, readonly) NSData *body; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Model/LSHTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPResponse 4 | @property (nonatomic, assign, readonly) NSInteger statusCode; 5 | @property (nonatomic, strong, readonly) NSDictionary *headers; 6 | @property (nonatomic, strong, readonly) NSData *body; 7 | @end 8 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Nocilla.h: -------------------------------------------------------------------------------- 1 | // 2 | // Nocilla.h 3 | // Nocilla 4 | // 5 | // Created by Robert Böhnke on 26/03/15. 6 | // Copyright (c) 2015 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Nocilla. 12 | FOUNDATION_EXPORT double NocillaVersionNumber; 13 | 14 | //! Project version string for Nocilla. 15 | FOUNDATION_EXPORT const unsigned char NocillaVersionString[]; 16 | 17 | #import "LSHTTPBody.h" 18 | #import "LSMatcheable.h" 19 | #import "LSNocilla.h" 20 | #import "LSStubRequestDSL.h" 21 | #import "LSStubResponseDSL.h" 22 | #import "NSData+Matcheable.h" 23 | #import "NSData+Nocilla.h" 24 | #import "NSRegularExpression+Matcheable.h" 25 | #import "NSString+Matcheable.h" 26 | #import "NSString+Nocilla.h" 27 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Stubs/LSStubRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPRequest.h" 4 | 5 | 6 | @class LSMatcher; 7 | @class LSStubRequest; 8 | @class LSStubResponse; 9 | 10 | @interface LSStubRequest : NSObject 11 | @property (nonatomic, strong, readonly) NSString *method; 12 | @property (nonatomic, strong, readonly) LSMatcher *urlMatcher; 13 | @property (nonatomic, strong, readonly) NSDictionary *headers; 14 | @property (nonatomic, strong, readwrite) LSMatcher *body; 15 | 16 | @property (nonatomic, strong) LSStubResponse *response; 17 | 18 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url; 19 | - (instancetype)initWithMethod:(NSString *)method urlMatcher:(LSMatcher *)urlMatcher; 20 | 21 | - (void)setHeader:(NSString *)header value:(NSString *)value; 22 | 23 | - (BOOL)matchesRequest:(id)request; 24 | @end 25 | -------------------------------------------------------------------------------- /Tests/Dependency/Nocilla/Nocilla/Stubs/LSStubResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPResponse.h" 3 | 4 | @interface LSStubResponse : NSObject 5 | 6 | @property (nonatomic, assign, readonly) NSInteger statusCode; 7 | @property (nonatomic, strong) NSData *body; 8 | @property (nonatomic, strong, readonly) NSDictionary *headers; 9 | 10 | @property (nonatomic, assign, readonly) BOOL shouldFail; 11 | @property (nonatomic, strong, readonly) NSError *error; 12 | 13 | - (id)initWithError:(NSError *)error; 14 | - (id)initWithStatusCode:(NSInteger)statusCode; 15 | - (id)initWithRawResponse:(NSData *)rawResponseData; 16 | - (id)initDefaultResponse; 17 | - (void)setHeader:(NSString *)header value:(NSString *)value; 18 | 19 | - (void)delay; 20 | - (void)go; 21 | - (void)waitForGo; 22 | @end 23 | -------------------------------------------------------------------------------- /Tests/KingfisherTests/ImageDrawingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDrawingTests.swift 3 | // Kingfisher 4 | // 5 | // Created by onevcat on 2018/10/26. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import XCTest 28 | @testable import Kingfisher 29 | 30 | class ImageDrawingTests: XCTestCase { 31 | 32 | func testImageResizing() { 33 | let result = testImage.kf.resize(to: CGSize(width: 20, height: 20)) 34 | XCTAssertEqual(result.size, CGSize(width: 20, height: 20)) 35 | } 36 | 37 | func testImageCropping() { 38 | let result = testImage.kf.crop(to: CGSize(width: 20, height: 20), anchorOn: .zero) 39 | XCTAssertEqual(result.size, CGSize(width: 20, height: 20)) 40 | } 41 | 42 | func testImageScaling() { 43 | XCTAssertEqual(testImage.kf.scale, 1) 44 | let result = testImage.kf.scaled(to: 2.0) 45 | #if os(macOS) 46 | // No scale supported on macOS. 47 | XCTAssertEqual(result.kf.scale, 1) 48 | XCTAssertEqual(result.size.height, testImage.size.height) 49 | #else 50 | XCTAssertEqual(result.kf.scale, 2) 51 | XCTAssertEqual(result.size.height, testImage.size.height / 2) 52 | #endif 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/KingfisherTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 8.3.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 3097 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/KingfisherTests/KingfisherTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "Nocilla.h" -------------------------------------------------------------------------------- /Tests/KingfisherTests/StringExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensionTests.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 16/8/14. 6 | // Copyright © 2019 Wei Wang. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Kingfisher 11 | 12 | class StringExtensionTests: XCTestCase { 13 | func testStringSHA256() { 14 | let s = "hello" 15 | XCTAssertEqual(s.kf.sha256, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/KingfisherTests/Utils/StubHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StubHelpers.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2018/10/12. 6 | // 7 | // Copyright (c) 2019 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | @discardableResult 30 | func stub(_ url: URL, 31 | data: Data, 32 | statusCode: Int = 200, 33 | length: Int? = nil, 34 | headers: [String: String] = [:] 35 | ) -> LSStubResponseDSL { 36 | var stubResult = stubRequest("GET", url.absoluteString as NSString) 37 | .andReturn(statusCode)? 38 | .withHeaders(headers)? 39 | .withBody(data as NSData) 40 | if let length = length { 41 | stubResult = stubResult?.withHeader("Content-Length", "\(length)") 42 | } 43 | return stubResult! 44 | } 45 | 46 | func delayedStub(_ url: URL, 47 | data: Data, 48 | statusCode: Int = 200, 49 | length: Int? = nil, 50 | headers: [String: String] = [:] 51 | ) -> LSStubResponseDSL { 52 | let result = stub(url, data: data, statusCode: statusCode, length: length, headers: headers) 53 | return result.delay()! 54 | } 55 | 56 | func stub(_ url: URL, errorCode: Int) { 57 | let error = NSError(domain: "stubError", code: errorCode, userInfo: nil) 58 | stub(url, error: error) 59 | } 60 | 61 | func stub(_ url: URL, error: any Error) { 62 | return stubRequest("GET", url.absoluteString as NSString).andFailWithError(error) 63 | } 64 | -------------------------------------------------------------------------------- /Tests/KingfisherTests/dancing-banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Tests/KingfisherTests/dancing-banana.gif -------------------------------------------------------------------------------- /Tests/KingfisherTests/single-frame.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/Tests/KingfisherTests/single-frame.gif -------------------------------------------------------------------------------- /fastlane/actions/extract_current_change_log.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | class ExtractCurrentChangeLogAction < Action 4 | require 'yaml' 5 | def self.run(params) 6 | yaml = File.read(params[:file]) 7 | data = YAML.load(yaml) 8 | version = data["version"] 9 | raise "The version should match in the input file".red unless (version and version == params[:version]) 10 | 11 | title = "#{version}" 12 | title = title + " - #{data["name"]}" if (data["name"] and not data["name"].empty?) 13 | 14 | return {:title => title, :version => version, :add => data["add"], :fix => data["fix"], :remove => data["remove"]} 15 | end 16 | 17 | ##################################################### 18 | # @!group Documentation 19 | ##################################################### 20 | 21 | def self.description 22 | "Extract change log information for a specified version." 23 | end 24 | 25 | def self.details 26 | "This action will check input version and change log. If everything goes well, the change log info will be returned." 27 | end 28 | 29 | def self.available_options 30 | [ 31 | FastlaneCore::ConfigItem.new(key: :version, 32 | env_name: "KF_EXTRACT_CURRENT_CHANGE_LOG_VERSION", 33 | description: "The target version which is needed to be extract", 34 | verify_block: proc do |value| 35 | raise "No version number is given, pass using `version: 'version_number'`".red unless (value and not value.empty?) 36 | end), 37 | FastlaneCore::ConfigItem.new(key: :file, 38 | env_name: "KF_EXTRACT_CURRENT_CHANGE_LOG_PRECHANGE_FILE", 39 | description: "Create a development certificate instead of a distribution one", 40 | default_value: "pre-change.yml") 41 | ] 42 | end 43 | 44 | def self.return_value 45 | "An object contains change log infomation. {version: }" 46 | end 47 | 48 | def self.is_supported?(platform) 49 | true 50 | end 51 | 52 | def self.authors 53 | ["onevcat"] 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /fastlane/actions/git_commit_all.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | class GitCommitAllAction < Action 4 | def self.run(params) 5 | Action.sh "git add -A" 6 | Actions.sh "git commit -am \"#{params[:message]}\"" 7 | end 8 | 9 | ##################################################### 10 | # @!group Documentation 11 | ##################################################### 12 | 13 | def self.description 14 | "Commit all unsaved changes to git." 15 | end 16 | 17 | def self.available_options 18 | [ 19 | FastlaneCore::ConfigItem.new(key: :message, 20 | env_name: "FL_GIT_COMMIT_ALL", 21 | description: "The git message for the commit", 22 | is_string: true) 23 | ] 24 | end 25 | 26 | def self.authors 27 | # So no one will ever forget your contribution to fastlane :) You are awesome btw! 28 | ["onevcat"] 29 | end 30 | 31 | def self.is_supported?(platform) 32 | true 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /fastlane/actions/sync_build_number_to_git.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | module SharedValues 4 | KF_BUILD_NUMBER = :BUILD_NUMBER 5 | end 6 | class SyncBuildNumberToGitAction < Action 7 | def self.is_git? 8 | Actions.sh 'git rev-parse HEAD' 9 | return true 10 | rescue 11 | return false 12 | end 13 | 14 | def self.run(params) 15 | if is_git? 16 | command = 'git rev-list HEAD --count' 17 | else 18 | raise "Not in a git repository." 19 | end 20 | build_number = (Actions.sh command).strip 21 | Fastlane::Actions::IncrementBuildNumberAction.run(build_number: build_number) 22 | Actions.lane_context[SharedValues::KF_BUILD_NUMBER] = build_number 23 | end 24 | 25 | def self.output 26 | [ 27 | ['KF_BUILD_NUMBER', 'The new build number'] 28 | ] 29 | end 30 | ##################################################### 31 | # @!group Documentation 32 | ##################################################### 33 | 34 | def self.description 35 | "Set the build version of your project to the same number of your total git commit count" 36 | end 37 | 38 | def self.authors 39 | ["onevcat"] 40 | end 41 | 42 | def self.is_supported?(platform) 43 | [:ios, :mac].include? platform 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /fastlane/actions/update_change_log.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | class UpdateChangeLogAction < Action 4 | def self.run(params) 5 | log = params[:log] 6 | raise "Invalid log object".red unless !log[:title].empty? and !log[:version].empty? 7 | 8 | readme = File.read(params[:changelogfile]) 9 | log_text = "## [#{log[:title]}](https://github.com/onevcat/Kingfisher/releases/tag/#{log[:version]}) (#{Time.now.strftime("%Y-%m-%d")})\n\n" 10 | 11 | des = "" 12 | add = log[:add].map { |i| "* #{i}" }.join("\n") unless log[:add].nil? 13 | des = des + "#### Add\n#{add}\n\n" unless add.nil? or add.empty? 14 | 15 | fix = log[:fix].map { |i| "* #{i}" }.join("\n") unless log[:fix].nil? 16 | des = des + "#### Fix\n#{fix}\n\n" unless fix.nil? or fix.empty? 17 | 18 | remove = log[:remove].map { |i| "* #{i}" }.join("\n") unless log[:remove].nil? 19 | des = des + "#### Remove\n#{remove}\n\n" unless remove.nil? or remove.empty? 20 | 21 | log_text = log_text + des 22 | 23 | File.open(params[:changelogfile], 'w') { |file| file.write(readme.sub("-----", "-----\n\n#{log_text}---")) } 24 | 25 | return {:title => log[:title], :text => des} 26 | end 27 | 28 | ##################################################### 29 | # @!group Documentation 30 | ##################################################### 31 | 32 | def self.description 33 | "Update the change log file with the content of log" 34 | end 35 | 36 | def self.details 37 | "Generally speaking, the log is return value of extract_current_change_log action" 38 | end 39 | 40 | def self.available_options 41 | [ 42 | FastlaneCore::ConfigItem.new(key: :log, 43 | env_name: "KF_UPDATE_CHANGE_LOG_LOG", 44 | description: "Change log extracted by pre change log file", 45 | is_string: false 46 | ), 47 | FastlaneCore::ConfigItem.new(key: :changelogfile, 48 | env_name: "KF_UPDATE_CHANGE_LOG_CHANGE_LOG_FILE", 49 | description: "The change log file, if not set, CHANGELOG.md will be used", 50 | default_value: "CHANGELOG.md") 51 | ] 52 | end 53 | 54 | def self.authors 55 | ["onevcat"] 56 | end 57 | 58 | def self.is_supported?(platform) 59 | true 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /images/kingfisher-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-1.jpg -------------------------------------------------------------------------------- /images/kingfisher-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-10.jpg -------------------------------------------------------------------------------- /images/kingfisher-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-2.jpg -------------------------------------------------------------------------------- /images/kingfisher-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-3.jpg -------------------------------------------------------------------------------- /images/kingfisher-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-4.jpg -------------------------------------------------------------------------------- /images/kingfisher-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-5.jpg -------------------------------------------------------------------------------- /images/kingfisher-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-6.jpg -------------------------------------------------------------------------------- /images/kingfisher-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-7.jpg -------------------------------------------------------------------------------- /images/kingfisher-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-8.jpg -------------------------------------------------------------------------------- /images/kingfisher-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/kingfisher-9.jpg -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/Kingfisher/a1704c5e75d563789b8f9f2f88cddee1c3ab4e49/images/logo.png --------------------------------------------------------------------------------