{"id":8475,"date":"2021-05-07T18:00:40","date_gmt":"2021-05-07T15:00:40","guid":{"rendered":"https:\/\/serialcoder.dev\/?p=8475"},"modified":"2022-08-19T14:17:17","modified_gmt":"2022-08-19T11:17:17","slug":"progressview-in-swiftui","status":"publish","type":"post","link":"https:\/\/serialcoder.dev\/text-tutorials\/swiftui\/progressview-in-swiftui\/","title":{"rendered":"Progress View 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\"> 6<\/span> <span class=\"rt-label rt-postfix\">mins<\/span><\/span>\n<p>The progress view in SwiftUI is a native view that was introduced in WWDC 2020 and makes it really easy to indicate visually the progress of long-running tasks that take time to complete.<\/p>\n\n\n\n<p>There are two kind of tasks to report progress for:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Those whose completion time can be determined.<\/li><li>Those whose completion time is impossible to predict.<\/li><\/ul>\n\n\n\n<p>For that reason, progress view exists in two types respectively; the <em>linear<\/em> and the <em>indeterminate<\/em>. The first one is a bar that gets filled according to the task&#8217;s completion amount, while the second is by default a circular activity indicator spinning around indefinitely until the task is over.<\/p>\n\n\n\n<p>We are going to see both in this post, starting with the indeterminate type of progress view. However, the demonstration will not stop in the built-in progress view styles only. We will also get to know how to create <em>custom progress view styles<\/em> in order to implement unique progress views.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The indeterminate progress view<\/h2>\n\n\n\n<p>The simplest way to present an indeterminate progress view (a spinner) is this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView()<\/pre><\/div>\n\n\n\n<p>The above will display the default spinner that we are familiar with. A message can easily be presented along with the spinner; we simply need to provide it as argument to the <code>ProgressView<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(\"Working...\")<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_1_spinner_message.png\" alt=\"Indeterminate progress view with message\"\/><\/figure><\/div>\n\n\n\n<p>We may want sometimes to change the default tint color of the progress view. The way to manage that depends on the target operating systems that the app is going to be available for. For instance:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If running in iOS 15 and above, then we set a tint color by applying the <code>tint(_:)<\/code> view modifier to the progress view.<\/li><li>For previous system versions, we have to use the <code>progressViewStyle(_:)<\/code> modifier passing as argument a <code>CircularProgressViewStyle<\/code> instance. Then we provide the tint color as argument to the latter.<\/li><\/ul>\n\n\n\n<p>To handle each case easily, we can use an <code>if #available<\/code> statement as shown next. Note that this wouldn&#8217;t be necessary if there is no need to support older system versions.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">if #available(iOS 15.0, *) {\n    ProgressView(\"Please wait...\")\n        .tint(.purple)\n} else {\n    ProgressView(\"Please wait...\")\n        .progressViewStyle(CircularProgressViewStyle(tint: .purple))\n}<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_2_spinner_color.png\" alt=\"Indeterminate progress view with tint color\"\/><\/figure><\/div>\n\n\n\n<p>To change the label&#8217;s color as well, we can use the <code>foregroundColor<\/code> modifier:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(\"Please wait...\")\n    .tint(.purple)\n    .foregroundColor(.green)<\/pre><\/div>\n\n\n\n<p>Besides all the above that demonstrate the most common kind of usage of the indeterminate progress view, it&#8217;s also possible to provide and display any other SwiftUI view instead of just a text as argument. The following creates a progress view that contains a button that&#8217;s supposed to stop the task. That button is now the progress view&#8217;s label:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView {\n    Button(action: {\n        \/\/ Do something to stop the task.\n    }) {\n        Text(\"Cancel download\")\n            .foregroundColor(.white)\n    }\n    .padding(8)\n    .background(Color.red)\n    .cornerRadius(5)\n}<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_3_spinner_button.png\" alt=\"Indeterminate progress view with custom button as label\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">The linear progress view<\/h2>\n\n\n\n<p>In order to indicate progress using the linear progress view style, we must necessarily provide it with two values:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The final value that indicates the task completion.<\/li><li>The current progress value towards the completion.<\/li><\/ul>\n\n\n\n<p>In its simplest form, a linear progress view can be presented like so:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(value: progress, total: 100)<\/pre><\/div>\n\n\n\n<p>The <code>progress<\/code> value indicates the current progress, and usually it&#8217;s a <code>@State<\/code> property:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">@State private var value: Double = 0<\/pre><\/div>\n\n\n\n<p>Similarly to the indeterminate progress view, we can provide a text label here too:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(\"Loading...\", value: progress, total: 100)<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_4_linear.gif\" alt=\"Linear progress view\"\/><\/figure><\/div>\n\n\n\n<p>Of course, the above is not enough in order to see the progress view animating; we have to make sure that the  <code>progress<\/code> value gets updated somehow.<\/p>\n\n\n\n<p>The color of the progress bar can be changed, but similarly to the previous progress style, the way to achieve it depends on the system version. For iOS 15 and above we have to use the <code>tint(_:)<\/code> modifier, but for earlier versions the <code>accentColor(_:)<\/code> modifier is what we have to apply.<\/p>\n\n\n\n<p>Note that only the progress bar&#8217;s color is changed with the view modifiers that were just mentioned. In order to change the label&#8217;s color there is another view modifier that has to be used; the <code>foregroundColor(_:)<\/code>.<\/p>\n\n\n\n<p>Once again, we can use an <code>if #available<\/code> statement so as to use the proper modifiers depending on the system version. We can avoid that if iOS 15 is the minimum required version for the app:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">if #available(iOS 15.0, *) {\n    ProgressView(\"Loading...\", value: progress, total: 100)\n        .tint(.gray)\n        .foregroundColor(.blue)\n} else {\n    ProgressView(\"Loading...\", value: progress, total: 100)\n        .accentColor(.gray)\n        .foregroundColor(.blue)\n}<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_5_linear_colored.gif\" alt=\"Linear progress view with tint color\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Custom progress view styles<\/h2>\n\n\n\n<p>The default appearance of the progress view can be overridden by implementing custom progress view styles. Such a style is actually a custom type, <em>a structure<\/em>, that conforms to the <em>ProgressViewStyle<\/em> protocol. The only requirement is to implement a method called <code>makeBody(configuration:)<\/code> and return either a progress view, or any other SwiftUI view customized the way we want it.<\/p>\n\n\n\n<p>The following snippet presents a custom progress view style. All it does is to add a background color to the progress bar, change the default tint color, add some padding and rounded corners:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">struct WithBackgroundProgressViewStyle: ProgressViewStyle {\n    func makeBody(configuration: Configuration) -&gt; some View {\n        ProgressView(configuration)\n            .padding(8)\n            .background(Color.gray.opacity(0.25))\n            .tint(.red)\n            .cornerRadius(8)\n    }\n}<\/pre><\/div>\n\n\n\n<p>See that a <code>ProgressView<\/code> instance is initialized with the <code>configuration<\/code> parameter value, it gets the style we desire by applying various view modifiers, and eventually it&#8217;s returned from the method. To use it, we just create a new progress view that will be using this style with the help of the <code>progressViewStyle<\/code> view modifier:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(value: progress, total: 100)\n    .progressViewStyle(WithBackgroundProgressViewStyle())<\/pre><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_6_linear_style1.gif\" alt=\"Linear progress view with custom style and background color\"\/><\/figure><\/div>\n\n\n\n<p>I mentioned previously that the return value of the <code>makeBody(configuration:)<\/code> method can be any SwiftUI view. With that in mind, you can see right next the implementation of another custom style. The view we return this time is a <code>ZStack<\/code> with two overlapping rounded rectangles. The first one is the background of the progress view, the other indicates the current progress that is used to increase the width accordingly:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">struct RoundedRectProgressViewStyle: ProgressViewStyle {\n    func makeBody(configuration: Configuration) -&gt; some View {\n        ZStack(alignment: .leading) {\n            RoundedRectangle(cornerRadius: 14)\n                .frame(width: 250, height: 28)\n                .foregroundColor(.blue)\n                .overlay(Color.black.opacity(0.5)).cornerRadius(14)\n            \n            RoundedRectangle(cornerRadius: 14)\n                .frame(width: CGFloat(configuration.fractionCompleted ?? 0) * 250, height: 28)\n                .foregroundColor(.yellow)\n        }\n        .padding()\n    }\n}<\/pre><\/div>\n\n\n\n<p>Using the above once again with the <code>progressViewStyle<\/code> modifier:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(\"Loading...\", value: progress, total: 100)\n    .progressViewStyle(RoundedRectProgressViewStyle())<\/pre><\/div>\n\n\n\n<p>The result is this:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_7_linear_style2.gif\" alt=\"Linear progress view with custom style and two rounded rectangle views\"\/><\/figure><\/div>\n\n\n\n<p>A linear progress view does not have to be always horizontal. To demonstrate that, here is one last custom style implementation that&#8217;s a bit different from the above two:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">struct CustomCircularProgressViewStyle: ProgressViewStyle {\n    func makeBody(configuration: Configuration) -&gt; some View {\n        ZStack {\n            Circle()\n                .trim(from: 0.0, to: CGFloat(configuration.fractionCompleted ?? 0))\n                .stroke(Color.blue, style: StrokeStyle(lineWidth: 3, dash: [10, 5]))\n                .rotationEffect(.degrees(-90))\n                .frame(width: 200)\n            \n            if let fractionCompleted = configuration.fractionCompleted {\n                Text(fractionCompleted &lt; 1 ?\n                        \"Completed \\(Int((configuration.fractionCompleted ?? 0) * 100))%\"\n                        : \"Done!\"\n                )\n                .fontWeight(.bold)\n                .foregroundColor(fractionCompleted &lt; 1 ? .orange : .green)\n                .frame(width: 180)\n            }\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p>The return value is once again a <code>ZStack<\/code>. We have a circular shape whose stroke is trimmed according to the current progress value; the higher the progress value, the more the circle is completed with a stroke.<\/p>\n\n\n\n<p>The second shape is a text, &#8220;sitting&#8221; at the center of the circle and showing the progress percentage, or the &#8220;Done&#8221; value on completion.<\/p>\n\n\n\n<p>See that in order to get the current progress we are using the <code>fractionCompleted<\/code> property from the <code>configuration<\/code> object. It&#8217;s value is in the range 0&#8230;1, but for indeterminate progress views it becomes nil. That&#8217;s why it&#8217;s necessary to unwrap its value before using it in the Text view right above.<\/p>\n\n\n\n<p>Using this custom style is similar to the previous cases:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \">ProgressView(\"Loading...\", value: progress, total: 100)\n    .progressViewStyle(CustomCircularProgressViewStyle())<\/pre><\/div>\n\n\n\n<p>And the result is this:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/post_media_and_files\/swiftui\/18_progressview\/progressview_8_linear_style3.gif\" alt=\"Linear progress view with custom style and filling circle with progress text\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>Presenting a progress view in SwiftUI is easy, especially if we want to stick to the system provided ones. We&#8217;ve met various options to customize it with view modifiers, but for unique progress views, implementing custom styles is the only way to go. You can unleash your imagination and create intuitive progress views that&#8217;d be the perfect match for the user interface of your apps. I presented a few simple styles in this post, but you can go as far as you want with it; it&#8217;s always up to you. In any case, I hope what you read here today to be proved helpful in your projects.<\/p>\n\n\n\n<p>Thanks for reading! ????\u200d????<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Find out how to show and configure a progress view in SwiftUI for long-running tasks, and create advanced progress views with custom styles.<\/p>\n","protected":false},"author":1,"featured_media":12995,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[410],"tags":[250,506,507,503,505,362],"class_list":["post-8475","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-swiftui","tag-activity-indicator","tag-indeterminate","tag-linear","tag-progress","tag-progress-view","tag-swiftui"],"_links":{"self":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8475","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=8475"}],"version-history":[{"count":0,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8475\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media\/12995"}],"wp:attachment":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media?parent=8475"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/categories?post=8475"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/tags?post=8475"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}