├── .scripts
├── validate.sh
├── lint.sh
├── install_swiftlint.sh
├── test.sh
└── create-xcframeworks.sh
├── Tests
├── Resources
│ ├── cat.gif
│ ├── swift.png
│ ├── video.mp4
│ ├── baseline.jpeg
│ ├── baseline.webp
│ ├── fixture.jpeg
│ ├── fixture.png
│ ├── image-p3.jpg
│ ├── img_751.heic
│ ├── grayscale.jpeg
│ ├── fixture-tiny.jpeg
│ ├── progressive.jpeg
│ ├── Snapshots
│ │ ├── s-circle.png
│ │ ├── s-sepia.png
│ │ ├── s-circle-border.png
│ │ ├── s-rounded-corners.png
│ │ ├── s-sepia-less-intense.png
│ │ ├── s-crop-left-orientation.jpg
│ │ ├── s-crop-left-orientation.png
│ │ └── s-rounded-corners-border.png
│ └── right-orientation.jpeg
├── NukeTests
│ ├── DeprecationTests.swift
│ ├── DataPublisherTests.swift
│ ├── ImageProcessorsTests
│ │ ├── AnonymousTests.swift
│ │ ├── DecompressionTests.swift
│ │ └── GaussianBlurTests.swift
│ ├── ImagePipelineTests
│ │ ├── ImagePipelineDecodingTests.swift
│ │ ├── ImagePipelineFormatsTests.swift
│ │ ├── ImagePipelineTaskDelegateTests.swift
│ │ ├── ImagePipelineProcessorTests.swift
│ │ └── ImagePipelineConfigurationTests.swift
│ ├── ImageDecoderRegistryTests.swift
│ ├── LinkedListTest.swift
│ ├── ImageEncoderTests.swift
│ └── RateLimiterTests.swift
├── Host
│ ├── AppDelegate.swift
│ ├── ViewController.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ └── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
├── MockImageEncoder.swift
├── NukePerformanceTests
│ ├── ImageRequestPerformanceTests.swift
│ ├── ImageCachePerformanceTests.swift
│ ├── DataCachePeformanceTests.swift
│ ├── ImageViewPerformanceTests.swift
│ ├── ImageProcessingPerformanceTests.swift
│ └── ImagePipelinePerformanceTests.swift
├── Info.plist
├── MockDataCache.swift
├── MockImageCache.swift
├── MockImageDecoder.swift
├── NukeExtensionsTests
│ └── NukeExtensionsTestsHelpers.swift
├── CombineExtensions.swift
├── MockProgressiveDataLoader.swift
├── MockDataLoader.swift
├── NukeExtensions.swift
├── MockImageProcessor.swift
└── ImagePipelineObserver.swift
├── Documentation
├── Nuke.docc
│ ├── Resources
│ │ ├── bench-01.png
│ │ └── bench-02.png
│ ├── Extensions
│ │ ├── ImageResponse-Extension.md
│ │ ├── DataLoader-Extension.md
│ │ ├── ImageTask-Extension.md
│ │ ├── ImagePiplelineCache-Extension.md
│ │ ├── ImagePipelineDelegate-Extension.md
│ │ ├── ImageRequest-Extension.md
│ │ └── ImagePipelineConfiguration-Extension.md
│ ├── Performance
│ │ └── Caching
│ │ │ └── caching.md
│ └── Customization
│ │ ├── ImageFormats
│ │ ├── image-formats.md
│ │ └── image-encoding.md
│ │ └── ImageProcessing
│ │ └── image-processing.md
├── NukeUI.docc
│ ├── Resources
│ │ └── nukeui-preview.png
│ ├── Extensions
│ │ ├── Image-Extension.md
│ │ ├── LazyImageView-Extensions.md
│ │ ├── FetchImage-Extensions.md
│ │ └── LazyImage-Extensions.md
│ └── NukeUI.md
├── NukeExtensions.docc
│ ├── Resources
│ │ └── pjpeg_demo.mp4
│ └── NukeExtensions.md
└── Migrations
│ ├── Nuke 6 Migration Guide.md
│ ├── Nuke 7 Migration Guide.md
│ ├── Nuke 11 Migration Guide.md
│ ├── Nuke 9 Migration Guide.md
│ └── Nuke 5 Migration Guide.md
├── Nuke.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── NukeUI Unit Tests.xcscheme
│ ├── Nuke Performance Tests.xcscheme
│ ├── NukeExtensions Tests.xcscheme
│ ├── Nuke Unit Tests.xcscheme
│ ├── Nuke Thread Safety Tests.xcscheme
│ ├── NukeVideo.xcscheme
│ ├── NukeUI.xcscheme
│ ├── NukeExtensions.xcscheme
│ └── Nuke Tests Host.xcscheme
├── .github
└── FUNDING.yml
├── Sources
├── Nuke
│ ├── Encoding
│ │ ├── ImageEncoders.swift
│ │ ├── ImageEncoding.swift
│ │ ├── ImageEncoders+Default.swift
│ │ └── ImageEncoders+ImageIO.swift
│ ├── Loading
│ │ └── DataLoading.swift
│ ├── Caching
│ │ ├── DataCaching.swift
│ │ └── ImageCaching.swift
│ ├── Processing
│ │ ├── ImageProcessors+Anonymous.swift
│ │ ├── ImageProcessors+Circle.swift
│ │ ├── ImageDecompression.swift
│ │ ├── ImageProcessors+GaussianBlur.swift
│ │ ├── ImageProcessors+RoundedCorners.swift
│ │ ├── ImageProcessors+Composition.swift
│ │ └── ImageProcessingOptions.swift
│ ├── Internal
│ │ ├── Atomic.swift
│ │ ├── Log.swift
│ │ ├── Extensions.swift
│ │ ├── DataPublisher.swift
│ │ ├── LinkedList.swift
│ │ ├── Operation.swift
│ │ ├── ImagePublisher.swift
│ │ └── ImageRequestKeys.swift
│ ├── Decoding
│ │ ├── ImageDecoders+Empty.swift
│ │ ├── ImageDecoderRegistry.swift
│ │ ├── ImageDecoding.swift
│ │ └── AssetType.swift
│ ├── Tasks
│ │ ├── TaskLoadData.swift
│ │ ├── AsyncPipelineTask.swift
│ │ ├── TaskFetchOriginalImage.swift
│ │ └── TaskFetchWithPublisher.swift
│ ├── ImageResponse.swift
│ └── Pipeline
│ │ └── ImagePipeline+Error.swift
├── NukeUI
│ └── LazyImageState.swift
└── NukeVideo
│ ├── ImageDecoders+Video.swift
│ └── AVDataAsset.swift
├── Package.swift
├── .swiftlint.yml
├── LICENSE
└── .gitignore
/.scripts/validate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ./temp/swiftlint lint --strict
4 |
--------------------------------------------------------------------------------
/Tests/Resources/cat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/cat.gif
--------------------------------------------------------------------------------
/Tests/Resources/swift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/swift.png
--------------------------------------------------------------------------------
/Tests/Resources/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/video.mp4
--------------------------------------------------------------------------------
/Tests/Resources/baseline.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/baseline.jpeg
--------------------------------------------------------------------------------
/Tests/Resources/baseline.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/baseline.webp
--------------------------------------------------------------------------------
/Tests/Resources/fixture.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/fixture.jpeg
--------------------------------------------------------------------------------
/Tests/Resources/fixture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/fixture.png
--------------------------------------------------------------------------------
/Tests/Resources/image-p3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/image-p3.jpg
--------------------------------------------------------------------------------
/Tests/Resources/img_751.heic:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/img_751.heic
--------------------------------------------------------------------------------
/Tests/Resources/grayscale.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/grayscale.jpeg
--------------------------------------------------------------------------------
/Tests/Resources/fixture-tiny.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/fixture-tiny.jpeg
--------------------------------------------------------------------------------
/Tests/Resources/progressive.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/progressive.jpeg
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-circle.png
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-sepia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-sepia.png
--------------------------------------------------------------------------------
/Tests/Resources/right-orientation.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/right-orientation.jpeg
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Resources/bench-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Documentation/Nuke.docc/Resources/bench-01.png
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Resources/bench-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Documentation/Nuke.docc/Resources/bench-02.png
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-circle-border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-circle-border.png
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-rounded-corners.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-rounded-corners.png
--------------------------------------------------------------------------------
/.scripts/lint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if which swiftlint >/dev/null; then
4 | swiftlint
5 | else
6 | echo "SwiftLint not installed"
7 | fi
8 |
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-sepia-less-intense.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-sepia-less-intense.png
--------------------------------------------------------------------------------
/Documentation/NukeUI.docc/Resources/nukeui-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Documentation/NukeUI.docc/Resources/nukeui-preview.png
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-crop-left-orientation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-crop-left-orientation.jpg
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-crop-left-orientation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-crop-left-orientation.png
--------------------------------------------------------------------------------
/Tests/Resources/Snapshots/s-rounded-corners-border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Tests/Resources/Snapshots/s-rounded-corners-border.png
--------------------------------------------------------------------------------
/Documentation/NukeExtensions.docc/Resources/pjpeg_demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kean/Nuke/HEAD/Documentation/NukeExtensions.docc/Resources/pjpeg_demo.mp4
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImageResponse-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImageResponse``
2 |
3 | ## Topics
4 |
5 | ### Related Types
6 |
7 | - ``ImageContainer``
8 |
--------------------------------------------------------------------------------
/Tests/NukeTests/DeprecationTests.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import XCTest
6 | @testable import Nuke
7 |
--------------------------------------------------------------------------------
/Nuke.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Documentation/NukeExtensions.docc/NukeExtensions.md:
--------------------------------------------------------------------------------
1 | # ``NukeExtensions``
2 |
3 | Nuke provides convenience extension for image views with multiple display options.
4 |
5 | ## Overview
6 |
7 | In this guide, you'll learn about these extensions and all of the available options.
8 |
--------------------------------------------------------------------------------
/Tests/Host/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import UIKit
6 |
7 | @UIApplicationMain
8 | class AppDelegate: UIResponder, UIApplicationDelegate {
9 |
10 | var window: UIWindow?
11 | }
12 |
--------------------------------------------------------------------------------
/.scripts/install_swiftlint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # -L to enable redirects
4 | echo "Installing SwiftLint by downloading a pre-compiled binary"
5 | curl -L 'https://github.com/realm/SwiftLint/releases/download/0.47.1/portable_swiftlint.zip' -o swiftlint.zip
6 | mkdir temp
7 | unzip swiftlint.zip -d temp
8 | rm -f swiftlint.zip
9 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/DataLoader-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/DataLoader``
2 |
3 |
4 | ## Topics
5 |
6 | ### Initializers
7 |
8 | - ``init(configuration:validate:)``
9 |
10 | ### Loading Data
11 |
12 | - ``loadData(with:didReceiveData:completion:)``
13 |
14 | ### Observing Events
15 |
16 | - ``delegate``
17 |
--------------------------------------------------------------------------------
/Nuke.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Documentation/NukeUI.docc/Extensions/Image-Extension.md:
--------------------------------------------------------------------------------
1 | # ``NukeUI/Image``
2 |
3 | ## Topics
4 |
5 | ### Initializers
6 |
7 | - ``init(_:)``
8 | - ``init(_:onCreated:)``
9 |
10 | ### Configuration
11 |
12 | - ``resizingMode(_:)``
13 | - ``videoRenderingEnabled(_:)``
14 | - ``videoLoopingEnabled(_:)``
15 | - ``animatedImageRenderingEnabled(_:)``
16 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImageTask-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImageTask``
2 |
3 | ## Topics
4 |
5 | ### Controlling the Task State
6 |
7 | - ``cancel()``
8 | - ``state-swift.property``
9 | - ``State-swift.enum``
10 | - ``priority``
11 | - ``ImageRequest/Priority-swift.enum``
12 |
13 | ### Task Progress
14 |
15 | - ``progress-swift.property``
16 | - ``Progress-swift.struct``
17 |
18 | ### General Task Information
19 |
20 | - ``request``
21 | - ``taskId``
22 | - ``description``
23 |
--------------------------------------------------------------------------------
/Tests/MockImageEncoder.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Nuke
7 |
8 | final class MockImageEncoder: ImageEncoding, @unchecked Sendable {
9 | let result: Data?
10 | var encodeCount = 0
11 |
12 | init(result: Data?) {
13 | self.result = result
14 | }
15 |
16 | func encode(_ image: PlatformImage) -> Data? {
17 | encodeCount += 1
18 | return result
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/Host/ViewController.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import UIKit
6 |
7 | class ViewController: UIViewController {
8 | override func viewDidLoad() {
9 | super.viewDidLoad()
10 | // Do any additional setup after loading the view, typically from a nib.
11 | }
12 |
13 | override func didReceiveMemoryWarning() {
14 | super.didReceiveMemoryWarning()
15 | // Dispose of any resources that can be recreated.
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: kean
4 | patreon: #
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImagePiplelineCache-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImagePipeline/Cache-swift.struct``
2 |
3 | ## Topics
4 |
5 | ### Accessing Cached Images
6 |
7 | - ``cachedImage(for:caches:)``
8 | - ``storeCachedImage(_:for:caches:)``
9 | - ``removeCachedImage(for:caches:)``
10 | - ``containsCachedImage(for:caches:)``
11 |
12 | ### Accessing Cached Data
13 |
14 | - ``cachedData(for:)``
15 | - ``storeCachedData(_:for:)``
16 | - ``removeCachedData(for:)``
17 | - ``containsData(for:)``
18 |
19 | ### Removing All
20 |
21 | - ``removeAll(caches:)``
22 |
23 | ### Cache Keys
24 |
25 | - ``makeImageCacheKey(for:)``
26 | - ``makeDataCacheKey(for:)``
27 |
--------------------------------------------------------------------------------
/Tests/NukePerformanceTests/ImageRequestPerformanceTests.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import XCTest
6 | import Nuke
7 |
8 | class ImageRequestPerformanceTests: XCTestCase {
9 | func testStoringRequestInCollections() {
10 | let urls = (0..<200_000).map { _ in return URL(string: "http://test.com/\(rnd(200))")! }
11 | let requests = urls.map { ImageRequest(url: $0) }
12 |
13 | measure {
14 | var array = [ImageRequest]()
15 | for request in requests {
16 | array.append(request)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImagePipelineDelegate-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImagePipelineDelegate``
2 |
3 | ## Topics
4 |
5 | ### Data Loading
6 |
7 | - ``dataLoader(for:pipeline:)-7xolj``
8 |
9 | ### Decoding and Encoding
10 |
11 | - ``imageDecoder(for:pipeline:)-2rbkl``
12 | - ``imageEncoder(for:pipeline:)-6uxsr``
13 |
14 | ### Caching
15 |
16 | - ``imageCache(for:pipeline:)-1i8cv``
17 | - ``dataCache(for:pipeline:)-2lnae``
18 | - ``cacheKey(for:pipeline:)-8k9a4``
19 | - ``willCache(data:image:for:pipeline:completion:)-7eg0n``
20 |
21 | ### Decompression
22 |
23 | - ``shouldDecompress(response:for:pipeline:)-3cw2f``
24 | - ``decompress(response:request:pipeline:)-lbbz``
25 |
--------------------------------------------------------------------------------
/.scripts/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eo pipefail
4 |
5 | scheme="Nuke"
6 |
7 | while getopts "s:d:" opt; do
8 | case $opt in
9 | s) scheme=${OPTARG};;
10 | d) destinations+=("$OPTARG");;
11 | #...
12 | esac
13 | done
14 | shift $((OPTIND -1))
15 |
16 | echo "scheme = ${scheme}"
17 | echo "destinations = ${destinations[@]}"
18 |
19 | xcodebuild -version
20 |
21 | xcodebuild build-for-testing -scheme "$scheme" -destination "${destinations[0]}"
22 |
23 | for destination in "${destinations[@]}";
24 | do
25 | echo "\nRunning tests for destination: $destination"
26 | xcodebuild test-without-building -scheme "$scheme" -destination "$destination"
27 | done
28 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Performance/Caching/caching.md:
--------------------------------------------------------------------------------
1 | # Caching
2 |
3 | Learn about cache layers in Nuke and how to configure them.
4 |
5 | ## Overview
6 |
7 | Nuke has three cache layers that you can configure to precisely match your app needs. The pipeline uses these caches when you request an image. Your app has advanced control over how images are stored and retrieved and direct access to all cache layers.
8 |
9 | ## Topics
10 |
11 | ### Overview
12 |
13 | -
14 | -
15 |
16 | ### Memory Cache
17 |
18 | - ``ImageCaching``
19 | - ``ImageCache``
20 | - ``ImageCacheKey``
21 |
22 | ### Disk Cache
23 |
24 | - ``DataCaching``
25 | - ``DataCache``
26 |
27 | ### Composite Cache
28 |
29 | - ``ImagePipeline/Cache-swift.struct``
30 |
--------------------------------------------------------------------------------
/Sources/Nuke/Encoding/ImageEncoders.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | /// A namespace with all available encoders.
8 | public enum ImageEncoders {}
9 |
10 | extension ImageEncoding where Self == ImageEncoders.Default {
11 | public static func `default`(compressionQuality: Float = 0.8) -> ImageEncoders.Default {
12 | ImageEncoders.Default(compressionQuality: compressionQuality)
13 | }
14 | }
15 |
16 | extension ImageEncoding where Self == ImageEncoders.ImageIO {
17 | public static func imageIO(type: AssetType, compressionRatio: Float = 0.8) -> ImageEncoders.ImageIO {
18 | ImageEncoders.ImageIO(type: type, compressionRatio: compressionRatio)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Documentation/NukeUI.docc/NukeUI.md:
--------------------------------------------------------------------------------
1 | # ``NukeUI``
2 |
3 | Image loading for SwiftUI, UIKit, and AppKit views.
4 |
5 | ## Overview
6 |
7 | There are two main views provided by the framework:
8 |
9 | - ``LazyImage`` for SwiftUI
10 | - ``LazyImageView`` for UIKit and AppKit
11 |
12 | ``LazyImage`` is designed similar to the native [`AsyncImage`](https://developer.apple.com/documentation/SwiftUI/AsyncImage), but it uses [Nuke](https://github.com/kean/Nuke) for loading images. You can take advantage of all of its features, such as caching, prefetching, task coalescing, smart background decompression, request priorities, and more.
13 |
14 | 
15 |
16 | ## Topics
17 |
18 | ### Essentials
19 |
20 | - ``LazyImage``
21 | - ``LazyImageView``
22 |
23 | ### Helpers
24 |
25 | - ``LazyImageState``
26 | - ``FetchImage``
27 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.9
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Nuke",
6 | platforms: [
7 | .iOS(.v13),
8 | .tvOS(.v13),
9 | .macOS(.v10_15),
10 | .watchOS(.v6),
11 | .visionOS(.v1),
12 | ],
13 | products: [
14 | .library(name: "Nuke", targets: ["Nuke"]),
15 | .library(name: "NukeUI", targets: ["NukeUI"]),
16 | .library(name: "NukeVideo", targets: ["NukeVideo"]),
17 | .library(name: "NukeExtensions", targets: ["NukeExtensions"])
18 | ],
19 | targets: [
20 | .target(name: "Nuke"),
21 | .target(name: "NukeUI", dependencies: ["Nuke"]),
22 | .target(name: "NukeVideo", dependencies: ["Nuke"]),
23 | .target(name: "NukeExtensions", dependencies: ["Nuke"])
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/Tests/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 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/Nuke/Loading/DataLoading.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | /// Fetches original image data.
8 | public protocol DataLoading: Sendable {
9 | /// - parameter didReceiveData: Can be called multiple times if streaming
10 | /// is supported.
11 | /// - parameter completion: Must be called once after all (or none in case
12 | /// of an error) `didReceiveData` closures have been called.
13 | func loadData(with request: URLRequest,
14 | didReceiveData: @escaping (Data, URLResponse) -> Void,
15 | completion: @escaping (Error?) -> Void) -> any Cancellable
16 | }
17 |
18 | /// A unit of work that can be cancelled.
19 | public protocol Cancellable: AnyObject, Sendable {
20 | func cancel()
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Nuke/Caching/DataCaching.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | /// Data cache.
8 | ///
9 | /// - important: The implementation must be thread safe.
10 | public protocol DataCaching: Sendable {
11 | /// Retrieves data from cache for the given key.
12 | func cachedData(for key: String) -> Data?
13 |
14 | /// Returns `true` if the cache contains data for the given key.
15 | func containsData(for key: String) -> Bool
16 |
17 | /// Stores data for the given key.
18 | /// - note: The implementation must return immediately and store data
19 | /// asynchronously.
20 | func storeData(_ data: Data, for key: String)
21 |
22 | /// Removes data for the given key.
23 | func removeData(for key: String)
24 |
25 | /// Removes all items.
26 | func removeAll()
27 | }
28 |
--------------------------------------------------------------------------------
/Documentation/NukeUI.docc/Extensions/LazyImageView-Extensions.md:
--------------------------------------------------------------------------------
1 | # ``NukeUI/LazyImageView``
2 |
3 | ## Topics
4 |
5 | ### Initializers
6 |
7 | - ``init(frame:)``
8 | - ``init(coder:)``
9 |
10 | ### Loading Images
11 |
12 | - ``url``
13 | - ``request``
14 | - ``cancel()``
15 | - ``reset()``
16 |
17 | ### Request Options
18 |
19 | - ``priority``
20 | - ``processors``
21 | - ``pipeline``
22 |
23 | ### Displaying Images
24 |
25 | - ``placeholderImage``
26 | - ``placeholderView``
27 | - ``placeholderViewPosition``
28 | - ``failureImage``
29 | - ``failureView``
30 | - ``failureViewPosition``
31 | - ``isProgressiveImageRenderingEnabled``
32 | - ``isResetEnabled``
33 | - ``transition-swift.property``
34 |
35 | ### Callbacks
36 |
37 | - ``onStart``
38 | - ``onProgress``
39 | - ``onPreview``
40 | - ``onSuccess``
41 | - ``onFailure``
42 | - ``onCompletion``
43 |
44 | ### Accessing Underlying Views
45 |
46 | - ``imageView``
47 |
--------------------------------------------------------------------------------
/Tests/MockDataCache.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Nuke
7 |
8 | final class MockDataCache: DataCaching, @unchecked Sendable {
9 | var store = [String: Data]()
10 | var readCount = 0
11 | var writeCount = 0
12 |
13 | func resetCounters() {
14 | readCount = 0
15 | writeCount = 0
16 | }
17 |
18 | func cachedData(for key: String) -> Data? {
19 | readCount += 1
20 | return store[key]
21 | }
22 |
23 | func containsData(for key: String) -> Bool {
24 | store[key] != nil
25 | }
26 |
27 | func storeData(_ data: Data, for key: String) {
28 | writeCount += 1
29 | store[key] = data
30 | }
31 |
32 | func removeData(for key: String) {
33 | store[key] = nil
34 | }
35 |
36 | func removeAll() {
37 | store.removeAll()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | opt_in_rules:
2 | - closure_spacing
3 | - convenience_type
4 | - empty_count
5 | - empty_string
6 | - explicit_init
7 | - fatal_error_message
8 | - first_where
9 | - identical_operands
10 | - joined_default_parameter
11 | - modifier_order
12 | - operator_usage_whitespace
13 | - overridden_super_call
14 | - pattern_matching_keywords
15 | - prohibited_super_call
16 | - toggle_bool
17 | - unavailable_function
18 | - vertical_parameter_alignment_on_call
19 |
20 | disabled_rules:
21 | - line_length
22 | - identifier_name
23 | - type_name
24 |
25 | nesting:
26 | type_level:
27 | warning: 2
28 |
29 | included:
30 | - Sources/
31 |
32 | file_length:
33 | warning: 1000
34 | error: 1500
35 |
36 | type_body_length:
37 | warning: 600
38 | error: 1000
39 |
40 | identifier_name:
41 | min_length:
42 | warning: 1
43 |
44 | reporter: "xcode"
45 |
--------------------------------------------------------------------------------
/Documentation/Migrations/Nuke 6 Migration Guide.md:
--------------------------------------------------------------------------------
1 | # Nuke 6 Migration Guide
2 |
3 | This guide is provided in order to ease the transition of existing applications using Nuke 5.x to the latest APIs, as well as explain the design and structure of new and changed functionality.
4 |
5 | ## Requirements
6 |
7 | - iOS 9.0, tvOS 9.0, macOS 10.11, watchOS 2.0
8 | - Xcode 9
9 | - Swift 4
10 |
11 | ## Overview
12 |
13 | Nuke 6 has a relatively small number of changes in the public API, chances are most of them are not going to affect your projects. Most of the deprecated APIs are kept in the project to ease the transition, however, they are going to be removed fairly soon.
14 |
15 | There were a lot of implementation details leaking into the public API in Nuke 5 (e.g. `Deduplicator` class, scheduling infrastructure) which were all made private in Nuke 6. If you were using any of those APIs you can always ping me with your questions on [Twitter](https://twitter.com/a_grebenyuk).
16 |
--------------------------------------------------------------------------------
/Tests/Host/Assets.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 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/Sources/Nuke/Processing/ImageProcessors+Anonymous.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | #if !os(macOS)
8 | import UIKit
9 | #else
10 | import AppKit
11 | #endif
12 |
13 | extension ImageProcessors {
14 | /// Processed an image using a specified closure.
15 | public struct Anonymous: ImageProcessing, CustomStringConvertible {
16 | public let identifier: String
17 | private let closure: @Sendable (PlatformImage) -> PlatformImage?
18 |
19 | public init(id: String, _ closure: @Sendable @escaping (PlatformImage) -> PlatformImage?) {
20 | self.identifier = id
21 | self.closure = closure
22 | }
23 |
24 | public func process(_ image: PlatformImage) -> PlatformImage? {
25 | closure(image)
26 | }
27 |
28 | public var description: String {
29 | "AnonymousProcessor(identifier: \(identifier)"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Customization/ImageFormats/image-formats.md:
--------------------------------------------------------------------------------
1 | # Image Formats
2 |
3 | Learn about image formats supported in Nuke and how to extend them.
4 |
5 | ## Overview
6 |
7 | Nuke has built-in support for basic image formats like `jpeg`, `png`, and `heif`. It also has the infrastructure for supporting a variety of custom image formats.
8 |
9 | Nuke can drive progressive decoding, animated image rendering, progressive animated image rendering, drawing vector images directly or converting them to bitmaps, parsing thumbnails included in the image containers, and more.
10 |
11 | ## Topics
12 |
13 | ### Supported Images
14 |
15 | -
16 | - ``PlatformImage``
17 | - ``AssetType``
18 |
19 | ### Decoding
20 |
21 | -
22 | - ``ImageDecoding``
23 | - ``ImageDecoders``
24 | - ``ImageDecodingError``
25 | - ``ImageDecodingContext``
26 | - ``ImageDecoderRegistry``
27 |
28 | ### Encoding
29 |
30 | -
31 | - ``ImageEncoding``
32 | - ``ImageEncoders``
33 | - ``ImageEncodingContext``
34 |
--------------------------------------------------------------------------------
/Sources/Nuke/Internal/Atomic.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | final class Atomic: @unchecked Sendable {
8 | private var _value: T
9 | private let lock: os_unfair_lock_t
10 |
11 | init(value: T) {
12 | self._value = value
13 | self.lock = .allocate(capacity: 1)
14 | self.lock.initialize(to: os_unfair_lock())
15 | }
16 |
17 | deinit {
18 | lock.deinitialize(count: 1)
19 | lock.deallocate()
20 | }
21 |
22 | var value: T {
23 | get {
24 | os_unfair_lock_lock(lock)
25 | defer { os_unfair_lock_unlock(lock) }
26 | return _value
27 | }
28 | set {
29 | os_unfair_lock_lock(lock)
30 | defer { os_unfair_lock_unlock(lock) }
31 | _value = newValue
32 | }
33 | }
34 |
35 | func withLock(_ closure: (inout T) -> U) -> U {
36 | os_unfair_lock_lock(lock)
37 | defer { os_unfair_lock_unlock(lock) }
38 | return closure(&_value)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImageRequest-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImageRequest``
2 |
3 | ## Image Processing
4 |
5 | Set ``ImageRequest/processors`` to apply one of the built-in processors that can be found in ``ImageProcessors`` namespace or a custom one.
6 |
7 | ```swift
8 | request.processors = [.resize(width: 320)]
9 | ```
10 |
11 | > Tip: See for more information on image processing.
12 |
13 | ## Topics
14 |
15 | ### Initializers
16 |
17 | - ``init(url:processors:priority:options:userInfo:)``
18 | - ``init(urlRequest:processors:priority:options:userInfo:)``
19 | - ``init(id:data:processors:priority:options:userInfo:)``
20 | - ``init(id:dataPublisher:processors:priority:options:userInfo:)``
21 | - ``init(stringLiteral:)``
22 |
23 | ### Options
24 |
25 | - ``processors``
26 | - ``priority-swift.property``
27 | - ``options-swift.property``
28 | - ``userInfo``
29 |
30 | ### Nested Types
31 |
32 | - ``Priority-swift.enum``
33 | - ``Options-swift.struct``
34 | - ``UserInfoKey``
35 | - ``ThumbnailOptions``
36 |
37 | ### Instance Properties
38 |
39 | - ``urlRequest``
40 | - ``url``
41 | - ``imageId``
42 | - ``description``
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2024 Alexander Grebenyuk
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 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Extensions/ImagePipelineConfiguration-Extension.md:
--------------------------------------------------------------------------------
1 | # ``Nuke/ImagePipeline/Configuration-swift.struct``
2 |
3 | ## Topics
4 |
5 | ### Initializers
6 |
7 | - ``init(dataLoader:)``
8 |
9 | ### Predefined Configurations
10 |
11 | To learn more about caching, see .
12 |
13 | - ``withDataCache``
14 | - ``withDataCache(name:sizeLimit:)``
15 | - ``withURLCache``
16 |
17 | ### Dependencies
18 |
19 | - ``dataLoader``
20 | - ``dataCache``
21 | - ``imageCache``
22 | - ``makeImageDecoder``
23 | - ``makeImageEncoder``
24 |
25 | ### Caching Options
26 |
27 | - ``dataCachePolicy``
28 | - ``ImagePipeline/DataCachePolicy``
29 | - ``isStoringPreviewsInMemoryCache``
30 |
31 | ### Other Options
32 |
33 | - ``isDecompressionEnabled``
34 | - ``isTaskCoalescingEnabled``
35 | - ``isRateLimiterEnabled``
36 | - ``isProgressiveDecodingEnabled``
37 | - ``isResumableDataEnabled``
38 | - ``callbackQueue``
39 |
40 | ### Global Options
41 |
42 | - ``isSignpostLoggingEnabled``
43 |
44 | ### Operation Queues
45 |
46 | - ``dataLoadingQueue``
47 | - ``dataCachingQueue``
48 | - ``imageProcessingQueue``
49 | - ``imageDecompressingQueue``
50 | - ``imageDecodingQueue``
51 | - ``imageEncodingQueue``
52 |
--------------------------------------------------------------------------------
/Sources/Nuke/Caching/ImageCaching.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | /// In-memory image cache.
8 | ///
9 | /// The implementation must be thread safe.
10 | public protocol ImageCaching: AnyObject, Sendable {
11 | /// Access the image cached for the given request.
12 | subscript(key: ImageCacheKey) -> ImageContainer? { get set }
13 |
14 | /// Removes all caches items.
15 | func removeAll()
16 | }
17 |
18 | /// An opaque container that acts as a cache key.
19 | ///
20 | /// In general, you don't construct it directly, and use ``ImagePipeline`` or ``ImagePipeline/Cache-swift.struct`` APIs.
21 | public struct ImageCacheKey: Hashable, Sendable {
22 | let key: Inner
23 |
24 | // This is faster than using AnyHashable (and it shows in performance tests).
25 | enum Inner: Hashable, Sendable {
26 | case custom(String)
27 | case `default`(MemoryCacheKey)
28 | }
29 |
30 | public init(key: String) {
31 | self.key = .custom(key)
32 | }
33 |
34 | public init(request: ImageRequest) {
35 | self.key = .default(MemoryCacheKey(request))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Tests/MockImageCache.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | @testable import Nuke
7 |
8 | class MockImageCache: ImageCaching, @unchecked Sendable {
9 | let queue = DispatchQueue(label: "com.github.Nuke.MockCache")
10 | var enabled = true
11 | var images = [AnyHashable: ImageContainer]()
12 | var readCount = 0
13 | var writeCount = 0
14 |
15 | init() {}
16 |
17 | func resetCounters() {
18 | readCount = 0
19 | writeCount = 0
20 | }
21 |
22 | subscript(key: ImageCacheKey) -> ImageContainer? {
23 | get {
24 | queue.sync {
25 | readCount += 1
26 | return enabled ? images[key] : nil
27 | }
28 | }
29 | set {
30 | queue.sync {
31 | writeCount += 1
32 | if let image = newValue {
33 | if enabled { images[key] = image }
34 | } else {
35 | images[key] = nil
36 | }
37 | }
38 | }
39 | }
40 |
41 | func removeAll() {
42 | images.removeAll()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Nuke/Encoding/ImageEncoding.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | #if canImport(UIKit)
6 | import UIKit
7 | #endif
8 |
9 | #if canImport(AppKit)
10 | import AppKit
11 | #endif
12 |
13 | import ImageIO
14 |
15 | // MARK: - ImageEncoding
16 |
17 | /// An image encoder.
18 | public protocol ImageEncoding: Sendable {
19 | /// Encodes the given image.
20 | func encode(_ image: PlatformImage) -> Data?
21 |
22 | /// An optional method which encodes the given image container.
23 | func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data?
24 | }
25 |
26 | extension ImageEncoding {
27 | public func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data? {
28 | if container.type == .gif {
29 | return container.data
30 | }
31 | return self.encode(container.image)
32 | }
33 | }
34 |
35 | /// Image encoding context used when selecting which encoder to use.
36 | public struct ImageEncodingContext: @unchecked Sendable {
37 | public let request: ImageRequest
38 | public let image: PlatformImage
39 | public let urlResponse: URLResponse?
40 | }
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## System
2 | .DS_Store
3 |
4 | ## Build generated
5 | build/
6 | DerivedData
7 | Nuke.xcodeproj/xcshareddata/xcbaselines/
8 | .swiftpm/
9 |
10 | ## Various settings
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 |
21 | ## Other
22 | *.xccheckout
23 | *.moved-aside
24 | *.xcuserstate
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 |
36 | ## Swift Package Manager
37 | #
38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39 | # Packages/
40 |
41 | .build/
42 |
43 |
44 | ## CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 |
51 | Pods/
52 |
53 |
54 | ## Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 |
58 | Carthage
59 |
--------------------------------------------------------------------------------
/Tests/NukeTests/DataPublisherTests.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import XCTest
6 | import Combine
7 | @testable import Nuke
8 |
9 | internal final class DataPublisherTests: XCTestCase {
10 |
11 | private var cancellable: (any Nuke.Cancellable)?
12 |
13 | func testInitNotStartsExecutionRightAway() {
14 | let operation = MockOperation()
15 | let publisher = DataPublisher(id: UUID().uuidString) {
16 | await operation.execute()
17 | }
18 |
19 | XCTAssertEqual(0, operation.executeCalls)
20 |
21 | let expOp = expectation(description: "Waits for MockOperation to complete execution")
22 | cancellable = publisher.sink { completion in expOp.fulfill() } receiveValue: { _ in }
23 | wait(for: [expOp], timeout: 0.2)
24 |
25 | XCTAssertEqual(1, operation.executeCalls)
26 | }
27 |
28 | private final class MockOperation: @unchecked Sendable {
29 |
30 | private(set) var executeCalls = 0
31 |
32 | func execute() async -> Data {
33 | executeCalls += 1
34 | await Task.yield()
35 | return Data()
36 | }
37 |
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Nuke/Processing/ImageProcessors+Circle.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | #if !os(macOS)
8 | import UIKit
9 | #else
10 | import AppKit
11 | #endif
12 |
13 | extension ImageProcessors {
14 |
15 | /// Rounds the corners of an image into a circle. If the image is not a square,
16 | /// crops it to a square first.
17 | public struct Circle: ImageProcessing, Hashable, CustomStringConvertible {
18 | private let border: ImageProcessingOptions.Border?
19 |
20 | /// - parameter border: `nil` by default.
21 | public init(border: ImageProcessingOptions.Border? = nil) {
22 | self.border = border
23 | }
24 |
25 | public func process(_ image: PlatformImage) -> PlatformImage? {
26 | image.processed.byDrawingInCircle(border: border)
27 | }
28 |
29 | public var identifier: String {
30 | let suffix = border.map { "?border=\($0)" }
31 | return "com.github.kean/nuke/circle" + (suffix ?? "")
32 | }
33 |
34 | public var description: String {
35 | "Circle(border: \(border?.description ?? "nil"))"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Nuke/Processing/ImageDecompression.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | enum ImageDecompression {
8 | static func isDecompressionNeeded(for response: ImageResponse) -> Bool {
9 | isDecompressionNeeded(for: response.image) ?? false
10 | }
11 |
12 | static func decompress(image: PlatformImage, isUsingPrepareForDisplay: Bool = false) -> PlatformImage {
13 | image.decompressed(isUsingPrepareForDisplay: isUsingPrepareForDisplay) ?? image
14 | }
15 |
16 | // MARK: Managing Decompression State
17 |
18 | #if swift(>=5.10)
19 | // Safe because it's never mutated.
20 | nonisolated(unsafe) static let isDecompressionNeededAK = malloc(1)!
21 | #else
22 | static let isDecompressionNeededAK = malloc(1)!
23 | #endif
24 |
25 | static func setDecompressionNeeded(_ isDecompressionNeeded: Bool, for image: PlatformImage) {
26 | objc_setAssociatedObject(image, isDecompressionNeededAK, isDecompressionNeeded, .OBJC_ASSOCIATION_RETAIN)
27 | }
28 |
29 | static func isDecompressionNeeded(for image: PlatformImage) -> Bool? {
30 | objc_getAssociatedObject(image, isDecompressionNeededAK) as? Bool
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Documentation/Nuke.docc/Customization/ImageFormats/image-encoding.md:
--------------------------------------------------------------------------------
1 | # Image Encoding
2 |
3 | To encode images, use types conforming to the ``ImageEncoding`` protocol:
4 |
5 | ```swift
6 | public protocol ImageEncoding {
7 | func encode(image: UIImage) -> Data?
8 | }
9 | ```
10 |
11 | There is currently no dedicated image encoder registry. Use the pipeline configuration to register custom decoders using ``ImagePipeline/Configuration-swift.struct/makeImageDecoder``.
12 |
13 | ## Built-In Image Encoders
14 |
15 | You can find all of the built-in encoders in the ``ImageEncoders`` namespace.
16 |
17 | ### ImageEncoders.Default
18 |
19 | ``ImageEncoders/Default`` encodes opaque images as `jpeg` and images with opacity as `png`. It can also be configured to use `heif` instead of `jpeg` using ``ImageEncoders/Default/isHEIFPreferred`` option.
20 |
21 | ### ImageEncoders.ImageIO
22 |
23 | ``ImageEncoders/ImageIO`` is an [Image I/O](https://developer.apple.com/documentation/imageio) based encoder.
24 |
25 | Image I/O is a system framework that allows applications to read and write most image file formats. This framework offers high efficiency, color management, and access to image metadata.
26 |
27 | ```swift
28 | let image: UIImage
29 | let encoder = ImageEncoders.ImageIO(type: .heif, compressionRatio: 0.8)
30 | let data = encoder.encode(image: image)
31 | ```
32 |
--------------------------------------------------------------------------------
/Sources/Nuke/Internal/Log.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import os
7 |
8 | func signpost(_ object: AnyObject, _ name: StaticString, _ type: OSSignpostType, _ message: @autoclosure () -> String) {
9 | guard ImagePipeline.Configuration.isSignpostLoggingEnabled else { return }
10 |
11 | let log = log.value
12 | let signpostId = OSSignpostID(log: log, object: object)
13 | os_signpost(type, log: log, name: name, signpostID: signpostId, "%{public}s", message())
14 | }
15 |
16 | func signpost(_ name: StaticString, _ work: () throws -> T) rethrows -> T {
17 | guard ImagePipeline.Configuration.isSignpostLoggingEnabled else { return try work() }
18 |
19 | let log = log.value
20 | let signpostId = OSSignpostID(log: log)
21 | os_signpost(.begin, log: log, name: name, signpostID: signpostId)
22 | let result = try work()
23 | os_signpost(.end, log: log, name: name, signpostID: signpostId)
24 | return result
25 | }
26 |
27 | private let log = Atomic(value: OSLog(subsystem: "com.github.kean.Nuke.ImagePipeline", category: "Image Loading"))
28 |
29 | enum Formatter {
30 | static func bytes(_ count: Int) -> String {
31 | bytes(Int64(count))
32 | }
33 |
34 | static func bytes(_ count: Int64) -> String {
35 | ByteCountFormatter().string(fromByteCount: count)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Nuke/Decoding/ImageDecoders+Empty.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | extension ImageDecoders {
8 | /// A decoder that returns an empty placeholder image and attaches image
9 | /// data to the image container.
10 | public struct Empty: ImageDecoding, Sendable {
11 | public let isProgressive: Bool
12 | private let assetType: AssetType?
13 |
14 | public var isAsynchronous: Bool { false }
15 |
16 | /// Initializes the decoder.
17 | ///
18 | /// - Parameters:
19 | /// - type: Image type to be associated with an image container.
20 | /// `nil` by default.
21 | /// - isProgressive: If `false`, returns nil for every progressive
22 | /// scan. `false` by default.
23 | public init(assetType: AssetType? = nil, isProgressive: Bool = false) {
24 | self.assetType = assetType
25 | self.isProgressive = isProgressive
26 | }
27 |
28 | public func decode(_ data: Data) throws -> ImageContainer {
29 | ImageContainer(image: PlatformImage(), type: assetType, data: data, userInfo: [:])
30 | }
31 |
32 | public func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
33 | isProgressive ? ImageContainer(image: PlatformImage(), type: assetType, data: data, userInfo: [:]) : nil
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/NukeUI/LazyImageState.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Nuke
7 | import SwiftUI
8 | import Combine
9 |
10 | /// Describes current image state.
11 | @MainActor
12 | public protocol LazyImageState {
13 | /// Returns the current fetch result.
14 | var result: Result? { get }
15 |
16 | /// Returns the fetched image.
17 | ///
18 | /// - note: In case pipeline has `isProgressiveDecodingEnabled` option enabled
19 | /// and the image being downloaded supports progressive decoding, the `image`
20 | /// might be updated multiple times during the download.
21 | var imageContainer: ImageContainer? { get }
22 |
23 | /// Returns `true` if the image is being loaded.
24 | var isLoading: Bool { get }
25 |
26 | /// The progress of the image download.
27 | var progress: FetchImage.Progress { get }
28 | }
29 |
30 | extension LazyImageState {
31 | /// Returns the current error.
32 | public var error: Error? {
33 | if case .failure(let error) = result {
34 | return error
35 | }
36 | return nil
37 | }
38 |
39 | /// Returns an image view.
40 | public var image: Image? {
41 | #if os(macOS)
42 | imageContainer.map { Image(nsImage: $0.image) }
43 | #else
44 | imageContainer.map { Image(uiImage: $0.image) }
45 | #endif
46 | }
47 | }
48 |
49 | extension FetchImage: LazyImageState {}
50 |
--------------------------------------------------------------------------------
/Sources/Nuke/Tasks/TaskLoadData.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | /// Wrapper for tasks created by `loadData` calls.
8 | final class TaskLoadData: AsyncPipelineTask, @unchecked Sendable {
9 | override func start() {
10 | if let data = pipeline.cache.cachedData(for: request) {
11 | let container = ImageContainer(image: .init(), data: data)
12 | let response = ImageResponse(container: container, request: request)
13 | self.send(value: response, isCompleted: true)
14 | } else {
15 | self.loadData()
16 | }
17 | }
18 |
19 | private func loadData() {
20 | guard !request.options.contains(.returnCacheDataDontLoad) else {
21 | return send(error: .dataMissingInCache)
22 | }
23 | let request = request.withProcessors([])
24 | dependency = pipeline.makeTaskFetchOriginalData(for: request).subscribe(self) { [weak self] in
25 | self?.didReceiveData($0.0, urlResponse: $0.1, isCompleted: $1)
26 | }
27 | }
28 |
29 | private func didReceiveData(_ data: Data, urlResponse: URLResponse?, isCompleted: Bool) {
30 | let container = ImageContainer(image: .init(), data: data)
31 | let response = ImageResponse(container: container, request: request, urlResponse: urlResponse)
32 | if isCompleted {
33 | send(value: response, isCompleted: isCompleted)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Nuke/Encoding/ImageEncoders+Default.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 |
7 | #if !os(macOS)
8 | import UIKit
9 | #else
10 | import AppKit
11 | #endif
12 |
13 | extension ImageEncoders {
14 | /// A default adaptive encoder which uses best encoder available depending
15 | /// on the input image and its configuration.
16 | public struct Default: ImageEncoding {
17 | public var compressionQuality: Float
18 |
19 | /// Set to `true` to switch to HEIF when it is available on the current hardware.
20 | /// `false` by default.
21 | public var isHEIFPreferred = false
22 |
23 | public init(compressionQuality: Float = 0.8) {
24 | self.compressionQuality = compressionQuality
25 | }
26 |
27 | public func encode(_ image: PlatformImage) -> Data? {
28 | guard let cgImage = image.cgImage else {
29 | return nil
30 | }
31 | let type: AssetType
32 | if cgImage.isOpaque {
33 | if isHEIFPreferred && ImageEncoders.ImageIO.isSupported(type: .heic) {
34 | type = .heic
35 | } else {
36 | type = .jpeg
37 | }
38 | } else {
39 | type = .png
40 | }
41 | let encoder = ImageEncoders.ImageIO(type: type, compressionRatio: compressionQuality)
42 | return encoder.encode(image)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Tests/MockImageDecoder.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Nuke
7 |
8 | class MockFailingDecoder: Nuke.ImageDecoding, @unchecked Sendable {
9 | func decode(_ data: Data) throws -> ImageContainer {
10 | throw MockError(description: "decoder-failed")
11 | }
12 | }
13 |
14 | class MockImageDecoder: ImageDecoding, @unchecked Sendable {
15 | private let decoder = ImageDecoders.Default()
16 |
17 | let name: String
18 |
19 | init(name: String) {
20 | self.name = name
21 | }
22 |
23 | func decode(_ data: Data) throws -> ImageContainer {
24 | try decoder.decode(data)
25 | }
26 |
27 | func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
28 | decoder.decodePartiallyDownloadedData(data)
29 | }
30 | }
31 |
32 | class MockAnonymousImageDecoder: ImageDecoding, @unchecked Sendable {
33 | let closure: (Data, Bool) -> PlatformImage?
34 |
35 | init(_ closure: @escaping (Data, Bool) -> PlatformImage?) {
36 | self.closure = closure
37 | }
38 |
39 | convenience init(output: PlatformImage) {
40 | self.init { _, _ in output }
41 | }
42 |
43 | func decode(_ data: Data) throws -> ImageContainer {
44 | guard let image = closure(data, true) else {
45 | throw ImageDecodingError.unknown
46 | }
47 | return ImageContainer(image: image)
48 | }
49 |
50 | func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
51 | closure(data, false).map { ImageContainer(image: $0) }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/NukeTests/ImageProcessorsTests/AnonymousTests.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import XCTest
6 | @testable import Nuke
7 |
8 | #if !os(macOS)
9 | import UIKit
10 | #endif
11 |
12 | class ImageProcessorsAnonymousTests: XCTestCase {
13 |
14 | func testAnonymousProcessorsHaveDifferentIdentifiers() {
15 | XCTAssertEqual(
16 | ImageProcessors.Anonymous(id: "1", { $0 }).identifier,
17 | ImageProcessors.Anonymous(id: "1", { $0 }).identifier
18 | )
19 | XCTAssertNotEqual(
20 | ImageProcessors.Anonymous(id: "1", { $0 }).identifier,
21 | ImageProcessors.Anonymous(id: "2", { $0 }).identifier
22 | )
23 | }
24 |
25 | func testAnonymousProcessorsHaveDifferentHashableIdentifiers() {
26 | XCTAssertEqual(
27 | ImageProcessors.Anonymous(id: "1", { $0 }).hashableIdentifier,
28 | ImageProcessors.Anonymous(id: "1", { $0 }).hashableIdentifier
29 | )
30 | XCTAssertNotEqual(
31 | ImageProcessors.Anonymous(id: "1", { $0 }).hashableIdentifier,
32 | ImageProcessors.Anonymous(id: "2", { $0 }).hashableIdentifier
33 | )
34 | }
35 |
36 | func testAnonymousProcessorIsApplied() throws {
37 | // Given
38 | let processor = ImageProcessors.Anonymous(id: "1") {
39 | $0.nk_test_processorIDs = ["1"]
40 | return $0
41 | }
42 |
43 | // When
44 | let image = try XCTUnwrap(processor.process(Test.image))
45 |
46 | // Then
47 | XCTAssertEqual(image.nk_test_processorIDs, ["1"])
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Nuke/Processing/ImageProcessors+GaussianBlur.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | #if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)
6 |
7 | import Foundation
8 | import CoreImage
9 |
10 | #if !os(macOS)
11 | import UIKit
12 | #else
13 | import AppKit
14 | #endif
15 |
16 | extension ImageProcessors {
17 | /// Blurs an image using `CIGaussianBlur` filter.
18 | public struct GaussianBlur: ImageProcessing, Hashable, CustomStringConvertible {
19 | private let radius: Int
20 |
21 | /// Initializes the receiver with a blur radius.
22 | ///
23 | /// - parameter radius: `8` by default.
24 | public init(radius: Int = 8) {
25 | self.radius = radius
26 | }
27 |
28 | /// Applies `CIGaussianBlur` filter to the image.
29 | public func process(_ image: PlatformImage) -> PlatformImage? {
30 | try? _process(image)
31 | }
32 |
33 | /// Applies `CIGaussianBlur` filter to the image.
34 | public func process(_ container: ImageContainer, context: ImageProcessingContext) throws -> ImageContainer {
35 | try container.map(_process(_:))
36 | }
37 |
38 | private func _process(_ image: PlatformImage) throws -> PlatformImage {
39 | try CoreImageFilter.applyFilter(named: "CIGaussianBlur", parameters: ["inputRadius": radius], to: image)
40 | }
41 |
42 | public var identifier: String {
43 | "com.github.kean/nuke/gaussian_blur?radius=\(radius)"
44 | }
45 |
46 | public var description: String {
47 | "GaussianBlur(radius: \(radius))"
48 | }
49 | }
50 | }
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/Tests/Host/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Sources/Nuke/Internal/Extensions.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import CryptoKit
7 |
8 | extension String {
9 | /// Calculates SHA1 from the given string and returns its hex representation.
10 | ///
11 | /// ```swift
12 | /// print("http://test.com".sha1)
13 | /// // prints "50334ee0b51600df6397ce93ceed4728c37fee4e"
14 | /// ```
15 | var sha1: String? {
16 | guard let input = self.data(using: .utf8) else {
17 | return nil // The conversion to .utf8 should never fail
18 | }
19 | let digest = Insecure.SHA1.hash(data: input)
20 | var output = ""
21 | for byte in digest {
22 | output.append(String(format: "%02x", byte))
23 | }
24 | return output
25 | }
26 | }
27 |
28 | extension URL {
29 | var isLocalResource: Bool {
30 | scheme == "file" || scheme == "data"
31 | }
32 | }
33 |
34 | extension OperationQueue {
35 | convenience init(maxConcurrentCount: Int) {
36 | self.init()
37 | self.maxConcurrentOperationCount = maxConcurrentCount
38 | }
39 | }
40 |
41 | extension ImageRequest.Priority {
42 | var taskPriority: TaskPriority {
43 | switch self {
44 | case .veryLow: return .veryLow
45 | case .low: return .low
46 | case .normal: return .normal
47 | case .high: return .high
48 | case .veryHigh: return .veryHigh
49 | }
50 | }
51 | }
52 |
53 | final class AnonymousCancellable: Cancellable {
54 | let onCancel: @Sendable () -> Void
55 |
56 | init(_ onCancel: @Sendable @escaping () -> Void) {
57 | self.onCancel = onCancel
58 | }
59 |
60 | func cancel() {
61 | onCancel()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Documentation/Migrations/Nuke 7 Migration Guide.md:
--------------------------------------------------------------------------------
1 | # Nuke 7 Migration Guide
2 |
3 | This guide is provided in order to ease the transition of existing applications using Nuke 6.x to the latest APIs, as well as explain the design and structure of new and changed functionality.
4 |
5 | This migration guide is still work in progress, the finished version is going to be available when Nuke 7 is finally released.
6 |
7 | ## Requirements
8 |
9 | - iOS 9.0, tvOS 9.0, macOS 10.11, watchOS 2.0
10 | - Xcode 9.2
11 | - Swift 4.0
12 |
13 | ## Overview
14 |
15 | Nuke 7 is the biggest release yet. It contains more features and refinements that all of the previous releases combined. There are a lot of new APIs in Nuke 7, fortunately, it's almost completely source-compatible with Nuke 6.
16 |
17 | > Source-compatibility was removed in [Nuke 7.5](https://github.com/kean/Nuke/releases/tag/7.5). The latest source-compatible release is [Nuke 7.4.2](https://github.com/kean/Nuke/releases/tag/7.4.2). The best way to migrate would be to either upgrade to Nuke 7.4.2 first, or to drop this [Deprecated.swift](https://gist.github.com/kean/a14ca485ce72bef0e50cbb2f36ec7d91) into your project and follow the instructions from the warnings.
18 |
19 | Most of the new APIs have `Image*` prefix. Some of the types with `Image*` prefix are new (e.g. `ImagePipeline` which replaced `Manager` and `Loader`), some were just renamed (e.g. `ImageRequest` instead of `Request`), and some are reimagining of old APIs (e.g. `ImageDecoding` instead of `Decoding`).
20 |
21 | If you're using a deprecated API you're going to see a deprecation message with a suggestion which new API you should use instead. All of the deprecated APIs work exactly as they used to in the previous versions. The only exception is `DataLoading` protocol which was replaced with a new version, but most of the apps are not using it directly.
22 |
--------------------------------------------------------------------------------
/Tests/Host/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Tests/NukeExtensionsTests/NukeExtensionsTestsHelpers.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import XCTest
6 | @testable import Nuke
7 | @testable import NukeExtensions
8 |
9 | #if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)
10 | extension XCTestCase {
11 | @MainActor
12 | func expectToFinishLoadingImage(with request: ImageRequest,
13 | options: ImageLoadingOptions? = nil,
14 | into imageView: ImageDisplayingView,
15 | completion: ((_ result: Result) -> Void)? = nil) {
16 | let expectation = self.expectation(description: "Image loaded for \(request)")
17 | NukeExtensions.loadImage(
18 | with: request,
19 | options: options,
20 | into: imageView,
21 | completion: { result in
22 | XCTAssertTrue(Thread.isMainThread)
23 | completion?(result)
24 | expectation.fulfill()
25 | })
26 | }
27 |
28 | @MainActor
29 | func expectToLoadImage(with request: ImageRequest, options: ImageLoadingOptions? = nil, into imageView: ImageDisplayingView) {
30 | expectToFinishLoadingImage(with: request, options: options, into: imageView) { result in
31 | XCTAssertTrue(result.isSuccess)
32 | }
33 | }
34 | }
35 |
36 | extension ImageLoadingOptions {
37 | @MainActor
38 | private static var stack = [ImageLoadingOptions]()
39 |
40 | @MainActor
41 | static func pushShared(_ shared: ImageLoadingOptions) {
42 | stack.append(ImageLoadingOptions.shared)
43 | ImageLoadingOptions.shared = shared
44 | }
45 |
46 | @MainActor
47 | static func popShared() {
48 | ImageLoadingOptions.shared = stack.removeLast()
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/Sources/Nuke/Processing/ImageProcessors+RoundedCorners.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import CoreGraphics
7 |
8 | #if !os(macOS)
9 | import UIKit
10 | #else
11 | import AppKit
12 | #endif
13 |
14 | extension ImageProcessors {
15 | /// Rounds the corners of an image to the specified radius.
16 | ///
17 | /// - important: In order for the corners to be displayed correctly, the image must exactly match the size
18 | /// of the image view in which it will be displayed. See ``ImageProcessors/Resize`` for more info.
19 | public struct RoundedCorners: ImageProcessing, Hashable, CustomStringConvertible {
20 | private let radius: CGFloat
21 | private let border: ImageProcessingOptions.Border?
22 |
23 | /// Initializes the processor with the given radius.
24 | ///
25 | /// - parameters:
26 | /// - radius: The radius of the corners.
27 | /// - unit: Unit of the radius.
28 | /// - border: An optional border drawn around the image.
29 | public init(radius: CGFloat, unit: ImageProcessingOptions.Unit = .points, border: ImageProcessingOptions.Border? = nil) {
30 | self.radius = radius.converted(to: unit)
31 | self.border = border
32 | }
33 |
34 | public func process(_ image: PlatformImage) -> PlatformImage? {
35 | image.processed.byAddingRoundedCorners(radius: radius, border: border)
36 | }
37 |
38 | public var identifier: String {
39 | let suffix = border.map { ",border=\($0)" }
40 | return "com.github.kean/nuke/rounded_corners?radius=\(radius)" + (suffix ?? "")
41 | }
42 |
43 | public var description: String {
44 | "RoundedCorners(radius: \(radius) pixels, border: \(border?.description ?? "nil"))"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Tests/CombineExtensions.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Nuke
6 | import Combine
7 |
8 | extension Publishers {
9 | struct Anonymous