<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksii Ratiiev</title>
    <description>The latest articles on DEV Community by Oleksii Ratiiev (@oleksii_ratiiev).</description>
    <link>https://dev.to/oleksii_ratiiev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2752079%2F06cf3e7e-d2ab-4f47-9a23-79b940793ec3.jpg</url>
      <title>DEV Community: Oleksii Ratiiev</title>
      <link>https://dev.to/oleksii_ratiiev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oleksii_ratiiev"/>
    <language>en</language>
    <item>
      <title>How to use .refreshable with TCA in Swift</title>
      <dc:creator>Oleksii Ratiiev</dc:creator>
      <pubDate>Fri, 24 Jan 2025 10:27:21 +0000</pubDate>
      <link>https://dev.to/oleksii_ratiiev/how-to-use-refreshable-with-tca-in-swift-3pon</link>
      <guid>https://dev.to/oleksii_ratiiev/how-to-use-refreshable-with-tca-in-swift-3pon</guid>
      <description>&lt;p&gt;&lt;strong&gt;What's the Issue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To implement pull-to-refresh in SwiftUI, you need to use the &lt;code&gt;.refreshable&lt;/code&gt; modifier, but here is the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you simply add the &lt;code&gt;.refreshable&lt;/code&gt; modifier to your &lt;code&gt;Scroll&lt;/code&gt; or &lt;code&gt;List&lt;/code&gt; view and send an action to perform the refresh request, you'll notice that the loader disappears immediately.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.refreshable {
     send(.refresh)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56i7sq3ghrcnz51p9kbk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56i7sq3ghrcnz51p9kbk.gif" alt="Loader disappears immediately GIF" width="360" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This happens because we don't wait for the completion of the request. To address this, we must call &lt;code&gt;.finish()&lt;/code&gt; on our send call and await it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.refreshable {
     await send(.refresh).finish()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhxcm4ju7sa9p8twt79p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhxcm4ju7sa9p8twt79p.gif" alt="Loader keep showing during request GIF" width="360" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Adding a Refresh Completion Notification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another useful thing is to get notified when the refresh has finished. To achieve this, let's add a state variable that will hold &lt;code&gt;true&lt;/code&gt; while the refresh is in progress. We'll also add an action, &lt;code&gt;.refreshFinished&lt;/code&gt;, which will be sent when the refresh scope is completed. We'll then update our state accordingly.&lt;br&gt;
State&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @ObservableState
    struct State: Hashable, Sendable {
        //...
        var isRefreshing: Bool = false
    }

    public enum Action : Equatable, Sendable, ViewAction {
        case view(ViewAction)

        public enum ViewAction: Equatable, Sendable {
            case refreshFinished
            case refresh
        }
    }

//later in reducer
        case .refreshFinished:
            state.isRefreshing = false
            return .none
        case .refresh:
            state.isRefreshing = true
            return .run { _ in
                try await clock.sleep(for: .seconds(2))
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To send &lt;code&gt;refreshFinished&lt;/code&gt;, we'll utilize the &lt;code&gt;defer&lt;/code&gt; keyword, which is basically the same as &lt;code&gt;try/finally&lt;/code&gt; block in other languages.&lt;br&gt;
View&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    var body: some View {
            List {
                ForEach(store.items, id: \.self) { item in
                    Text(item)
                }
            }
            .refreshable {
                defer { send(.refreshFinished) }
                await send(.refresh).finish()
            }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implementing Refresh Cancellation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's add another feature to allow users to cancel the refresh. Since we now have a state variable, we can simply add a cancel button whenever the refresh is in progress and send a &lt;code&gt;.cancelRefreshTapped&lt;/code&gt; action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                if store.isRefreshing {
                    withAnimation(.easeIn) {
                        Button("Cancel") {
                            send(.cancelRefreshTapped)
                        }
                    }
                }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also need to create a &lt;code&gt;CancelID&lt;/code&gt; to identify which request to cancel&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private enum CancelID { case refreshRequest }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and mark the refresh effect as &lt;code&gt;.cancellable&lt;/code&gt;, providing the appropriate &lt;code&gt;CancelID&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        case .refresh:
            state.isRefreshing = true
            return .run { _ in
                try await clock.sleep(for: .seconds(2))
            }
            .cancellable(id: CancelID.refreshRequest)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to cancel the request when the &lt;code&gt;.cancelRefreshTapped&lt;/code&gt; action is received.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        case .cancelRefreshTapped:
            return .cancel(id: CancelID.refreshRequest)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it! We now have a fully functional pull-to-refresh implementation with proper handling of loading states, completion notifications, and cancellation.&lt;br&gt;
Full Code can be found &lt;a href="https://github.com/OleksiiRatiiev/OleksiiRatiiev_BlogSamples/blob/main/Swift/TCA/RefreshableView.swift" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
Similar &lt;a href="https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/CaseStudies/SwiftUICaseStudies/03-Effects-Refreshable.swift" rel="noopener noreferrer"&gt;example&lt;/a&gt; from TCA Case Studies repo&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback Welcome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, please leave a reaction. If you have any alternative approaches to implementing this, I would appreciate your comments. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About This Blog&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I started this blog to share the things I learn and believe can be helpful to others, so follow for more. Thanks (˶ᵔ ᵕ ᵔ˶)&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
