I’m currently learning SwiftUI and want to develop my own app. I have designed a LoginView
and a LoginHandler
that should take care of all the logic behind a login. When the user enters the wrong username/password, an Alert should appear on the screen. I solved this with the state variable loginError
. But now comes the tricky part, as i want to pass a binding of this variable to my login function in the LoginHandler
. Take a look at the following code:
import SwiftUI
struct LoginView: View
{
@EnvironmentObject var loginHandler: LoginHandler
@State private var username: String = ""
@State private var password: String = ""
@State private var loginError: Bool = false
...
private func login()
{
loginHandler.login(username: username, password: password, error: $loginError)
}
}
I am now trying to change the value of error inside my login function:
import Foundation
import SwiftUI
class LoginHandler: ObservableObject
{
public func login(username: String, password: String, error: Binding<Bool>)
{
error = true
}
}
But I’m getting the error
Cannot assign to value: ‘error’ is a ‘let’ constant
which makes sense I think because you can’t edit the parameters in swift. I have also tried _error = true
because I once saw the underscore in combination with a binding, but this doesn’t worked either.
But then I came up with a working solution: error.wrappedValue = true
. My only problem with that is the following statement from Apples Developer Documentation:
This property provides primary access to the value’s data. However, you don’t access wrappedValue directly. Instead, you use the property variable created with the @Binding attribute.
Although I’m super happy that it works, I wonder if there is any better way to solve this situation?
Update 20.3.21: New edge case
In the comment section I mentioned a case where you don’t know how many times your function will be used. I will now provide a little code example:
Imagine a list of downloadable files (DownloadView
) that you will get from your backend:
import SwiftUI
struct DownloadView: View
{
@EnvironmentObject var downloadHandler: DownloadHandler
var body: some View
{
VStack
{
ForEach(downloadHandler.getAllDownloadableFiles())
{
file in DownloadItemView(file: file)
}
}
}
}
Every downloadable file has a name, a small description and its own download button:
import SwiftUI
struct DownloadItemView: View
{
@EnvironmentObject var downloadHandler: DownloadHandler
@State private var downloadProgress: Double = -1
var file: File
var body: some View
{
HStack
{
VStack
{
Text(file.name)
Text(file.description)
}
Spacer()
if downloadProgress < 0
{
// User can start Download
Button(action: {
downloadFile()
})
{
Text("Download")
}
}
else
{
// User sees download progress
ProgressView(value: $downloadProgress)
}
}
}
func downloadFile()
{
downloadHandler.downloadFile(file: file, progress: $downloadProgress)
}
}
And now finally the ‘DownloadHandler’:
import Foundation
import SwiftUI
class DownloadHandler: ObservableObject
{
public func downloadFile(file: File, progress: Binding<Double>)
{
// Example for changing the value
progress = 0.5
}
}
2
Answers
You can update parameters of a function as well, here is an example, this not using Binding or State, it is inout!
So with this method or example you can!
I see what you’re trying to do, but it will cause problems later on down the line because you’re dealing with State here. Now one solution would be:
error
to the class, but then you would have theusername
andpassword
in one spot and the error in another.The ideal solution then is to abstract it all away in the same spot. Take away all of the properties from your view and have it like this:
Then in your class:
The only left for you to do is to update the
username
and password` and you can do that with this for exampleEdit: Adding an update for the edge case:
I didn’t add the progress to it because I’m short on time but I think this conveys the idea. You can always abstract away the individual identity needed by other views.
Either way, let me know if this was helpful.