{"id":8832,"date":"2021-06-10T18:00:01","date_gmt":"2021-06-10T15:00:01","guid":{"rendered":"https:\/\/serialcoder.dev\/?p=8832"},"modified":"2021-06-13T10:46:22","modified_gmt":"2021-06-13T07:46:22","slug":"asyncimage-in-swiftui","status":"publish","type":"post","link":"https:\/\/serialcoder.dev\/text-tutorials\/swiftui\/asyncimage-in-swiftui\/","title":{"rendered":"AsyncImage in SwiftUI"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">\u23f1 Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">mins<\/span><\/span>\n<p>At the time of writing this post, WWDC21 is still in progress. And as it turns out, this year&#8217;s announcements reveal really great and exciting news for developers. Among them, SwiftUI seems that has been equipped well with new APIs, views and tools that will undoubtedly make building apps with it an even more easier and fun task.<\/p>\n\n\n\n<p>A new and quite interesting view introduced in this, third release of SwiftUI, is the <em>AsyncImage<\/em>. As the name makes it pretty obvious, this view displays images after having fetched them from a remote URL. Doing so has been traditionally a manual task, but now AsyncImage does all the work behind the scenes until the image has been presented in the view. The AsyncImage API is simple, yet flexible enough; it provides options to display a placeholder image while waiting for the remote one, deal with potential errors, show the downloaded image animated, and of course, style the image as we like using view modifiers.<\/p>\n\n\n\n<p>There is only one downside, and that is that fetched images <em>are not cached<\/em> for future use. AsyncImage view downloads the remote image whenever it&#8217;s about to be displayed. That&#8217;s okay for images that will be shown to users just a few times. But for remote images that an app presents often, it&#8217;s not recommended to always fetch them in real time. A manual implementation to fetch and cache images is still necessary.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hands-on to the AsyncImage<\/h2>\n\n\n\n<p>Time to play around a bit with the AsyncImage and see what it does. The simplest way to use it is like so:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">AsyncImage(url: url)<\/pre><\/div>\n\n\n\n<p>The <code>url<\/code> argument is a URL object pointing to a remote image:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">let url = URL(string: \"https:\/\/images.pexels.com\/photos\/8016369\/pexels-photo-8016369.jpeg\")<\/pre><\/div>\n\n\n\n<p>If you add the above to a SwiftUI view and run it in a simulator, or just live preview it, you&#8217;ll see the image being displayed after a few moments. However, that&#8217;s not so much user friendly; a default gray colored box is shown temporarily as a placeholder. We need to indicate to our users that an asynchronous operation is running possibly with a progress view and some message, or display a more proper placeholder image until the remote one has been fetched.<\/p>\n\n\n\n<p>Let&#8217;s focus on using a placeholder image first. In order to achieve that, there&#8217;s another initializer to use: <code>init(url:scale:content:placeholder:)<\/code>.<\/p>\n\n\n\n<p>The first argument is the URL to the image, and the second is the scale to use for the image. The default scale value is 1. The other two arguments are closures, which I&#8217;ll explain more about after having seen that new initializer in action:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">AsyncImage(url: url) { image in\n    image\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n        .clipShape(RoundedRectangle(cornerRadius: 15))\n        .padding()\n} placeholder: {\n    placeholderImage()\n}<\/pre><\/div>\n\n\n\n<p>I&#8217;m not providing a value for the image scale here; I skip it, so the default one will be used instead.<\/p>\n\n\n\n<p>Let&#8217;s talk about the two closures now. The argument of the first closure is a SwiftUI Image view containing the image after it has been downloaded. We present it in the closure&#8217;s body, and as you can notice, we can use any view modifier that will style the image appropriately.<\/p>\n\n\n\n<p>The second closure is the place to add an Image view with a placeholder image. This one is going to be displayed for as long as we wait for the remote one to be fetched. It will keep being displayed in case the actual image cannot be loaded for some reason. In this example I&#8217;m calling the <code>placeholderImage()<\/code> method, which is the next one:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">@ViewBuilder\nfunc placeholderImage() -&gt; some View {\n    Image(systemName: \"photo\")\n        .renderingMode(.template)\n        .resizable()\n        .aspectRatio(contentMode: .fit)\n        .frame(width: 150, height: 150)\n        .foregroundColor(.gray)\n}<\/pre><\/div>\n\n\n\n<p>The <code>placeholderImage()<\/code> method returns a SwiftUI view, therefore it&#8217;s marked with the <code>@ViewBuilder<\/code> attribute. As a placeholder image I&#8217;m using an SF Symbol.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/swiftui-posts-images\/asyncImage_1_placeholder.gif\" alt=\"AsyncImage displaying a placeholder while fetching a remote image\"\/><\/figure>\n\n\n\n<p><em>Credits: Photo by Lucy from <a href=\"https:\/\/www.pexels.com\/photo\/nature-summer-sun-garden-8016369\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pexels<\/a>.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">AsyncImage and the AsyncImagePhase<\/h2>\n\n\n\n<p>AsyncImage has another initializer that allows to get the image load results in phases: <code>init(url:scale:transaction:content:)<\/code>.<\/p>\n\n\n\n<p>See the following example:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">AsyncImage(url: url) { phase in\n    switch phase {\n        case .success(let image):\n            image\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .clipShape(RoundedRectangle(cornerRadius: 15))\n                .padding()\n\n        case .failure(let error):\n            Text(error.localizedDescription)\n\n        case .empty:\n            waitView()\n\n        @unknown default:\n            EmptyView()\n    }\n}<\/pre><\/div>\n\n\n\n<p>Here I&#8217;m supplying only the URL and the <code>content<\/code> argument, which is a closure. The closure&#8217;s argument is an <em>AsyncImagePhase<\/em> value, where AsyncImagePhase is an enum with cases that represent certain load phases.<\/p>\n\n\n\n<p>In fact, you can see all available cases of the AsyncImagePhase in the <code>switch<\/code> statement right above. The first one is the case where the image has been successfully fetched. The associated value of the case is a SwiftUI Image view, which we can decorate using any view modifiers necessary.<\/p>\n\n\n\n<p>The second case contains the potential error that has might occurred. In this example I&#8217;m just presenting the error description in a Text view.<\/p>\n\n\n\n<p>The last case called <code>.empty<\/code> represents the state of the AsyncImage view when there is not a loaded image yet. Here, we can add either a placeholder view, a progress view, or anything else fitting to the app. In the above example I&#8217;m calling the <code>waitView()<\/code> method, which is the following:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">@ViewBuilder\nfunc waitView() -&gt; some View {\n    VStack {\n        ProgressView()\n            .progressViewStyle(CircularProgressViewStyle(tint: .indigo))\n        \n        Text(\"Fetching image...\")\n    }\n}<\/pre><\/div>\n\n\n\n<p>Its purpose is to present a progress view along with a text.<\/p>\n\n\n\n<p>See that there is also an <code>@unknown default<\/code> case in the <code>switch<\/code> statement; that&#8217;s necessary to handle any new values that might be added to the AsyncImagePhase enum in the future. An EmptyView is the best candidate to handle such a case.<\/p>\n\n\n\n<p>The above AsyncImage implementation results to this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/swiftui-posts-images\/asyncImage_2_phases.gif?i=514822835\" alt=\"AsyncImage presenting progress view and message while fetching remote image\"\/><\/figure>\n\n\n\n<p>In general, notice that the above way to initialize an AsyncImage view provides greater control over the view&#8217;s results. In particular,  we can actually detect when an error has occurred with this one and therefore display a different view; something that we can&#8217;t do using the placeholder initializer met in the previous part.<\/p>\n\n\n\n<p>But that&#8217;s not the only benefit of this initializer. Through the <code>transaction<\/code> argument we can pass an animation that will be put in motion when the phase changes. Right next you can see the same example as before, only an animation instance is provided to the <code>transaction<\/code> argument this time:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \">AsyncImage(url: url,\n           transaction: Transaction(animation: .easeInOut(duration: 2.5))\n) { phase in\n    switch phase {\n        case .success(let image):\n            image\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .clipShape(RoundedRectangle(cornerRadius: 15))\n                .padding()\n\n        case .failure(let error):\n            Text(error.localizedDescription)\n\n        case .empty:\n            waitView()\n\n        @unknown default:\n            EmptyView()\n    }\n}<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/swiftui-posts-images\/asyncImage_3_transaction.gif?i=184031129\" alt=\"AsyncImage animating the presentation of the remote image after fetching\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>This is pretty much everything about using  the AsyncImage view in order to load and display remote images. There are two things I didn&#8217;t mention in the introduction; the first one is that AsyncImage uses the shared URLSession instance to fetch an image from the given URL. The second, and probably the frustrating news, is that AsyncImage is available in iOS 15 and above. Nevertheless, it&#8217;s a great new view that will be useful beyond any doubt, and an amazing new addition to the SwiftUI arsenal. Thank you for reading!<\/p>\n\n\n\n<p class=\"has-background\" style=\"background-color:#333333\">You can find this post published on\u00a0<a href=\"https:\/\/gabth.medium.com\/asyncimage-in-swiftui-962b6a4393be\" target=\"_blank\" rel=\"noreferrer noopener\">Medium<\/a>\u00a0too!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Read about the brand new AsyncImage view in the third release of SwiftUI, and load and display remote images asynchronously and effortlessly.<\/p>\n","protected":false},"author":1,"featured_media":8889,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[410],"tags":[529,530,531,362],"class_list":["post-8832","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-swiftui","tag-asyncimage","tag-asyncimagephase","tag-remote-image","tag-swiftui"],"_links":{"self":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8832","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/comments?post=8832"}],"version-history":[{"count":0,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8832\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media\/8889"}],"wp:attachment":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media?parent=8832"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/categories?post=8832"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/tags?post=8832"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}