SwiftUI: We’re Loading, We’re Loading… | by Michael Long | Mar, 2022

Find out how to get your API calls to load as soon as… and solely as soon as.

UIKit and UIViewControllers gave us fairly a couple of choices in regard to controlling lifecycle occasions: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear, and so forth.

SwiftUI, then again, principally provides us onAppear and onDisappear. So if we need to load some knowledge for a view, we usually find yourself doing one thing like the next:

struct MyAccountListView: View {    @StateObject var viewModel = MyAccountListViewModel()    var physique: some View 
Record
ForEach(viewModel.accounts, id: .id) account in
NavigationLink(vacation spot: Particulars(account))
AccountListCellView(account: account)



.onAppear
viewModel.load()


}

Simply name load() in onAppear, and all is properly with the world. Proper?

Effectively, should you’ve finished SwiftUI for awhile, then you definitely in all probability know that the reply isn’t fairly that easy. And whereas a lot of the following options are comparatively easy, I’ve been seeing sufficient questions (and questionable options) throughout the web to recommend that they’re not fairly that apparent both.

So let’s get began. First, we have to perceive the difficulty at hand.

There and again once more

The primary, and most evident drawback lies with our navigation hyperlinks. Click on on an “account” within the listing and also you go to a brand new account particulars web page. However what occurs whenever you return from that web page?

Appropriate. Your view “seems” once more and as such the request to load your knowledge can be made once more.

This drawback could be exacerbated by the truth that SwiftUI (for causes identified solely to SwiftUI) may also name onAppear and onDisappear handlers greater than as soon as throughout a given transition. It’s gotten higher with this in 3.0, however it will possibly nonetheless occur.

And it doesn’t actually matter why, does it? We nonetheless have the navigation subject and we nonetheless need to load our knowledge one time, and one time solely.

So what can we do about it?

Flags

Effectively, should you’ve been programming greater than a few days, the primary (and most evident resolution) is to achieve for the hammer in our toolbox and set a flag. Take into account.

class MyAccountListViewModel: ObservableObject     @Printed var accounts: [Account] = []    personal var loading = true    func load() 
guard loading else return
shouldLoad = false
// load our knowledge right here

Case closed. Drawback solved. However this resolution, as options go, leaves a bit to be desired in that we’ve got to declare the variable in our viewModel, guard on it, after which bear in mind to reset our flag.

And it’s simply finicky sufficient that we’d in all probability need to write a couple of additional unit checks simply in an effort to ensure that we’ve gotten all the things right.

All in all it’s a bit… properly, let’s simply say it’s not very elegant. Can we do higher?

Atomics

Effectively, we may import the brand new Atomics library and remove the additional project assertion.

personal var loading = ManagedAtomic(true)func load() 
guard loading.change(false, ordering: .relaxed) else return
// load our knowledge right here

The change operate on the atomic worth will set loading to the the brand new worth (false), however however return the unique worth for analysis. It eliminates the necessity for an additional line of code, however it does so at the price of some complexity and using a library with which many Swift builders may not be conversant.

It’s additionally overkill on this scenario, as this code is very unlikely to be reentrant and referred to as throughout a number of threads.

dispatch_once

Within the outdated days, again when large Goal-C packages nonetheless walked the earth, we may use GCD and dispatch_once to make sure that a given block of code can be referred to as one time, and one time solely.

var token: dispatch_once_t = 0func load() 
dispatch_once(&token)
// load our knowledge right here

Sadly, dispatch_once was deprecated in Swift 3.0, and making an attempt to make use of dispatch_once_t right this moment provides you an error, telling you to make use of lazy variables as a substitute. We may write our personal model to deal with one of these scenario, however… lazy variables?

Let’s take into consideration that.

Lazy Variables

Lazy variables usually are not instantiated till they’re used, and Swift ensures that stated initialization will solely happen as soon as. Sounds precisely just like the conduct we want.

So what if we change our load operate with a lazily loaded operate?

class MyAccountListViewModel: ObservableObject   @Printed var accounts: [Account] = []  lazy var load: () -> Void = 
// load our knowledge right here
return
()

Right here we create a lazy variable with a closure that performs our load operate after which returns an empty closure. The () added to the tip ensures that the closure itself is evaluated when the variable is accessed.

So with this resolution our loading code known as the primary time our lazy operate is evaluated after which the empty closure can be used every time load() known as once more.

Be aware that we may nonetheless cross a worth to the load operate if wanted, noting, in fact, that our stub closure returned would additionally must mirror an empty, unused worth _ in .

This resolution is… not dangerous. It eliminates the additional flag variable and guard, on the expense of being a bit difficult and getting our loading routine referred to as purely as a side-effect of the intial lazy analysis.

Calling it as soon as

In fact, the easiest way to make sure that our code is just executed as soon as is to solely name it as soon as. Take into account the next modifications to our view mannequin.

class MyAccountListViewModel: ObservableObject     enum State 
case loading
case loaded([Account])
case empty(String)
case error(String)
@Printed var state: State = .loading func load()
// load our knowledge right here

Be aware our state enumeration and the truth that we’re now dealing with errors, empty states, and the like. Which, to be sincere, are all issues we’d in all probability must do in actual life.

Now try the corresponding change to our view.

struct MyAccountListLoadingView: View     @StateObject var viewModel = MyAccountListViewModel()    var physique: some View 
swap viewModel.state
case .loaded(let accounts):
AccountListView(accounts: accounts)
case .empty(let message):
MessageView(message: message, colour: .grey)
case .error(let message):
MessageView(message: message, colour: .purple)
case .loading:
ProgressView()
.onAppear
viewModel.load()



Right here we show totally different views relying on the state of our view mannequin, and that onAppear is now hooked up to our ProgressView. For the reason that preliminary state of our view mannequin is .loading, the ProgressView “seems” and our load operate known as.

As soon as the accounts are loaded, the progress view is eliminated and changed with our account listing view (or an error message or empty message).

However in any case the view internet hosting the onLoad modifier is eliminated and as such load() won’t ever be referred to as once more.

I wrote about this method at some size in Using View Model Protocols in SwiftUI? You’re Doing it Wrong. There I additionally defined how this technique can be utilized with protocols to assist with testing and mocking knowledge. Test it out.

In fact, should you’re paranoid, you can use this method and one of many earlier strategies simply in an effort to be completely optimistic load will solely be referred to as as soon as. (Form of a belt and suspenders method.)

Pull to Refresh

One other good factor about our remaining method is that it makes implementing behaviors like pull-to-refresh easy and straightforward.

Simply name load() within the view mannequin once more and when it’s completed load will replace the outcome state once more with new knowledge or an error or a message.

You may reset the state to .loading, however that might present our authentic progress view in addition to the pull-to-refresh spinner, which in all probability isn’t the very best person expertise.

Completion Block

So there you’ve gotten it. A handful of the way to resolve our drawback.

Obtained certainly one of your individual? Inform me about it within the feedback. And, in fact, clap and subscribe if you wish to see extra.

Till subsequent time.

More Posts