{"id":13536898,"url":"https://github.com/dmytro-anokhin/url-image","last_synced_at":"2025-08-20T16:30:54.783Z","repository":{"id":40623350,"uuid":"190926086","full_name":"dmytro-anokhin/url-image","owner":"dmytro-anokhin","description":"AsyncImage before iOS 15. Lightweight, pure SwiftUI Image view, that displays an image downloaded from URL, with auxiliary views and local cache.","archived":false,"fork":false,"pushed_at":"2024-07-08T14:59:45.000Z","size":1391,"stargazers_count":1125,"open_issues_count":15,"forks_count":98,"subscribers_count":16,"default_branch":"main","last_synced_at":"2024-12-12T12:02:40.634Z","etag":null,"topics":["ios","macos","swift","swift-package-manager","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmytro-anokhin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-08T19:17:26.000Z","updated_at":"2024-12-11T09:24:38.000Z","dependencies_parsed_at":"2024-09-30T21:50:33.696Z","dependency_job_id":null,"html_url":"https://github.com/dmytro-anokhin/url-image","commit_stats":{"total_commits":510,"total_committers":1,"mean_commits":510.0,"dds":0.0,"last_synced_commit":"20abda498bf8fe78631b1057b77078147598962b"},"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Furl-image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Furl-image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Furl-image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Furl-image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmytro-anokhin","download_url":"https://codeload.github.com/dmytro-anokhin/url-image/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230438184,"owners_count":18225870,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","macos","swift","swift-package-manager","swiftui"],"created_at":"2024-08-01T09:00:51.458Z","updated_at":"2024-12-19T13:03:26.896Z","avatar_url":"https://github.com/dmytro-anokhin.png","language":"Swift","funding_links":[],"categories":["Samples","🌎 by the community","Swift","Image","🛠 Examples"],"sub_categories":["Content","Helpers"],"readme":"# URLImage\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdmytro-anokhin%2Furl-image%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/dmytro-anokhin/url-image)\n[![Follow me on Twitter](https://img.shields.io/twitter/url?label=dmytroanokhin\u0026style=social\u0026url=https%3A%2F%2Ftwitter.com%2Fdmytroanokhin)](https://twitter.com/dmytroanokhin)\n\n\n`URLImage` is a SwiftUI view that displays an image downloaded from provided URL. `URLImage` manages downloading remote image and caching it locally, both in memory and on disk, for you.\n\nUsing `URLImage` is dead simple:\n\n```swift\nURLImage(url: url) { image in\n    image\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n}\n```\n\nTake a look at some examples in [the demo app](https://github.com/dmytro-anokhin/url-image-demo).\n\n# Table of Contents\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [View Customization](#view-customization)\n    - [Options](#options)\n    - [Image Information](#image-information)\n    - [Zoom In](#zoom-in)\n- [Cache](#cache) \n    - [Store Use cases](#store-use-cases) \n- [Advanced](#advanced)\n    - [Start Loading](#start-loading)\n    - [Make Your Own URLImage](#make-your-own-urlimage)\n    - [Fetching an Image](#fetching-an-image)\n    - [Download an Image in iOS 14 Widget](#download-an-image-in-ios-14-widget)\n- [Migration Notes v2 to v3](#migration-notes-v2-to-v3)\n- [Common Issues](#common-issues)\n- [Reporting a Bug](#reporting-a-bug)\n- [Requesting a Feature](#requesting-a-feature)\n- [Contributing](#contributing)\n\n## Features\n- SwiftUI image view for remote images;\n- Local image cache;\n- Fully customizable including placeholder, progress indication, error, and the image view;\n- Control over various download aspects for better performance.\n\n## Installation\n\n`URLImage` can be installed using Swift Package Manager.\n\n1. In Xcode open **File/Swift Packages/Add Package Dependency...** menu.\n\n2. Copy and paste the package URL:\n\n```\nhttps://github.com/dmytro-anokhin/url-image\n```\n\nFor more details refer to [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) documentation.\n\n## Usage\n\nYou can create `URLImage` with URL and a [`ViewBuilder`](https://developer.apple.com/documentation/swiftui/viewbuilder) to display downloaded image.\n\n```swift\nimport URLImage // Import the package module\n\nlet url: URL = //...\n\nURLImage(url) { image in\n    image\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n}\n```\n\n*Note: first argument of the `URLImage` initialiser is of `URL` type, if you have a `String` you must first create a `URL` object.*\n\n### View Customization\n\n`URLImage` view manages and transitions between 4 download states: \n\n- Empty state, when download has not started yet, or there is nothing to display;\n- In Progress state to indicate download process;\n- Failure state in case there is an error;\n- Content to display the image.\n\nEach of this states has a separate view. You can customize one or more using `ViewBuilder` arguments.\n\n```swift\nURLImage(item.imageURL) {\n    // This view is displayed before download starts\n    EmptyView()\n} inProgress: { progress in\n    // Display progress\n    Text(\"Loading...\")\n} failure: { error, retry in\n    // Display error and retry button\n    VStack {\n        Text(error.localizedDescription)\n        Button(\"Retry\", action: retry)\n    }\n} content: { image in\n    // Downloaded image\n    image\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n}\n```\n\n### Options\n\n`URLImage` allows to control certain aspects using `URLImageOptions` structure. Things like whenever to download image or use cached, when to start and cancel download, how to configure network request, what is the maximum pixel size, etc.\n\n`URLImageOptions` is the environment value and can be set using `\\.urlImageOptions` key path.\n\n```swift\nURLImage(url) { image in\n    image\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n}\n.environment(\\.urlImageOptions, URLImageOptions(\n    maxPixelSize: CGSize(width: 600.0, height: 600.0)\n))\n```\n\nSetting `URLImageOptions` in the environment value allows to set options for a whole or a part of your views hierarchy.\n\n```swift\n@main\nstruct MyApp: App {\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environment(\\.urlImageOptions, URLImageOptions(\n                    maxPixelSize: CGSize(width: 600.0, height: 600.0)\n                ))\n        }\n    }\n}\n```\n\n### Image Information\n\nYou can use `ImageInfo` structure if you need information about an image, like actual size, or access the underlying `CGImage` object. `ImageInfo` is an argument of `content` view builder closure. \n\n```swift\nURLImage(item.imageURL) { image, info in\n    if info.size.width \u003c 1024.0 {\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n    } else {\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fill)\n    }\n}\n```\n\n### Zoom In\n\nIf you want to add ability to scale the image consider checking [AdvancedScrollView](https://github.com/dmytro-anokhin/advanced-scrollview) package.\n\n```swift\nimport AdvancedScrollView\nimport URLImage\n\nURLImage(url) { image in\n    AdvancedScrollView(magnificationRange: 1.0...4.0) { _ in\n        image\n    }\n}\n```\n\n## Cache\n\n`URLImage` can also cache images to lower network bandwith or for offline use.\n\nBy default, `URLImage` uses protocol cache policy, i.e. Cache-Control HTTP header and `URLCache`. This corresponds to how images work on web and requires network connection.\n\nAlternatively, if you want to view images offline, you must configure the file store. When configured, `URLImage` will no longer use protocol cache policy, and instead follow `URLImageOptions.FetchPolicy` setting.\n\n```swift\nimport URLImage\nimport URLImageStore\n\n@main\nstruct MyApp: App {\n\n    var body: some Scene {\n\n        let urlImageService = URLImageService(fileStore: URLImageFileStore(),\n                                          inMemoryStore: URLImageInMemoryStore())\n\n        return WindowGroup {\n            FeedListView()\n                .environment(\\.urlImageService, urlImageService)\n        }\n    }\n}\n```\n\nMake sure to include `URLImageStore` library under \"Frameworks, Libraries,and Embedded Content\" of your target settings.\n\n### Store Use Cases\n\nYou may ask when to use protocol or custom cache. `URLImage` designed to serve two use cases:\n\nUse protocol cache policy when an app can only work connected to the internet. Ecommerce apps, such as shopping, travel, event reservation apps, etc., work like this. Following protocol cache policy you can be sure that images are cached in a way that your CDN defines, can still be accessed quickly, and don't take unnecessary space on user devices.\n\nConfigure `URLImageStore` for content that needs to be accessed offline or downloaded in background. This can be a reader app, you probably want to download articles before user opens them, maybe while the app is in the background. This content should stay for a considerably long period of time.\n\n## Advanced\n\n### Start Loading\n\n`URLImage` starts loading when the image view is rendered. In some cases (like with `List`) you may want to start loading when view appears and cancel when it disappears. You can customize this using `URLImageOptions.LoadOptions` options. You can combine multiple to achieve behaviour that fits your UI best.\n\n```swift\nList(/* ... */) {\n    // ...\n}\n    .environment(\\.urlImageOptions, URLImageOptions(loadOptions: [ .loadOnAppear, .cancelOnDisappear ]))\n``` \n\nNote: versions prior to 3.1 start loading on appearance and cancel when view disappears. Version 3.1 starts loading when the view renders. This is because `onAppear` and `onDisappear` callbacks are quite unpredictable without context.\n\n### Make Your Own URLImage\n\nAlternatively you can make your own `URLImage` to customize appearance and behaviour for your needs. \n\n```swift\nstruct MyURLImage: View {\n\n    @ObservedObject private var remoteImage: RemoteImage\n\n    init(service: URLImageService, url: URL) {\n        remoteImage = service.makeRemoteImage(url: url, identifier: nil, options: URLImageOptions())\n    }\n\n    var body: some View {\n        ZStack {\n            switch remoteImage.loadingState {\n                case .success(let value):\n                    value.image\n\n                default:\n                    EmptyView()\n            }\n        }\n        .onAppear {\n            remoteImage.load()\n        }\n    }\n}\n```\n\nYou can access service environment value from enclosing view: `@Environment(\\.urlImageService) var service: URLImageService`.\n\n### Fetching an Image\n\nYou may want to download an image without a view. This is possible using the `RemoteImagePublisher` object. The `RemoteImagePublisher` can cache images for future use by the `URLImage` view.\n\nDownload an image as `CGImage` and ignore any errors:\n\n```swift\ncancellable = URLImageService.shared.remoteImagePublisher(url)\n    .tryMap { $0.cgImage }\n    .catch { _ in\n        Just(nil)\n    }\n    .sink { image in\n        // image is CGImage or nil\n    }\n```\n\nDownload multiple images as an array of `[CGImage?]`:\n\n```swift\nlet publishers = urls.map { URLImageService.shared.remoteImagePublisher($0) }\n\ncancellable = Publishers.MergeMany(publishers)\n    .tryMap { $0.cgImage }\n    .catch { _ in\n        Just(nil)\n    }\n    .collect()\n    .sink { images in\n        // images is [CGImage?]\n    }\n```\n\nWhen downloading image using the `RemoteImagePublisher` object all options apply as they do for the `URLImage` object. Be default downloaded image will be cached on the disk. This can speedup displaying images on later stage of your app. Also, this is currently the only supported way to display images in iOS 14 widgets.\n\n### Download an Image in iOS 14 Widget\n\nUnfortunately views in WidgetKit can not run asynchronous operations: https://developer.apple.com/forums/thread/652581. The recommended way is to load your content, including images, in `TimelineProvider`.\n\nYou can still use `URLImage` for this. The idea is that you load image in `TimelineProvider` using the `RemoteImagePublisher` object, and display it in the `URLImage` view.\n\n## Migration Notes v2 to v3\n\n- `URLImage` initialiser now omits an argument label for the first parameter, making `URLImage(url: url)` just `URLImage(url)`.\n- `URLImage` initialiser now uses `ViewBuilder` attribute for closures that construct views.\n- `URLImageOptions` now passed in the environment, instead of as an argument. Custom identifier can still be passed as an argument of `URLImage`.\n- By default `URLImage` uses protocol cache policy and `URLCache`. This won't store images for offline usage. You can configure the file store as described in [cache](#cache) section.\n- Swift Package Manager is now the only officially supported dependency manager.\n\n## Common Issues\n\n### Image reloads when view reloads\n\nThis is a common issue if you use `URLImage` alongside `TextField` or another control that updates a state that triggers view update. Because `URLImage` is asynchronous and initially empty, it will reset to empty state before displaying downloaded image. To avoid this, setup `URLImageInMemoryStore` somewhere in your `App`.\n\n```\nimport SwiftUI\nimport URLImage\nimport URLImageStore\n\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        let urlImageService = URLImageService(fileStore: nil, inMemoryStore: URLImageInMemoryStore())\n\n        return WindowGroup {\n            ContentView()\n                .environment(\\.urlImageService, urlImageService)\n        }\n    }\n}\n```\n\nNote: you can reset cached image using `removeImageWithURL`, `removeImageWithIdentifier`, or `removeAllImages` methods of `URLImageInMemoryStore`.\n\n### Image in navigation/toolbar displayed as single color rectangle\n\nThis is not a bug. Navigation/toolbar uses `.renderingMode(.template)` to display images as templates (renders all non-transparent pixels as the foreground color). The way to reset it is to specify `.renderingMode(.original)`:\n\n```\nURLImage(url) { image in\n    image.renderingMode(.original)\n}\n```\n\n## Reporting a Bug\n\nUse GitHub issues to report a bug. Include this information when possible:\n\nSummary and/or background;\nOS and what device you are using;\nVersion of URLImage library;\nWhat you expected would happen;\nWhat actually happens;\nAdditional information:\nScreenshots or video demonstrating a bug;\nCrash log;\nSample code, try isolating it so it compiles without dependancies;\nTest data: if you use public resource provide URLs of the images.\n\nPlease make sure there is a reproducible scenario. Ideally provide a sample code. And if you submit a sample code - make sure it compiles ;)\n\n## Requesting a Feature\n\nUse GitHub issues to request a feature.\n\n## Contributing\n\nContributions are welcome. Please create a GitHub issue before submitting a pull request to plan and discuss implementation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmytro-anokhin%2Furl-image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmytro-anokhin%2Furl-image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmytro-anokhin%2Furl-image/lists"}