How to use optional #State variable with binding parameter in SwiftUI - ios

I am trying to use an optional #State variable with a TextField. This is my code:
struct StorageView: View {
#State private var storedValue: String?
var body: some View {
VStack {
TextField("Type a value", text: $storedValue)
}
}
}
When I use this code, I get the following error:
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'
I have tried the following code for both the values to be optional, but nothing seems to work:
TextField("Type a value", text: $storedValue ?? "")
TextField("Type a value", text: Binding($storedValue)!)
How would I go about using an optional #State variable with a binding? Any help with this is appreciated.

How would I go about using an optional #State variable with a binding? Any help with this is appreciated.
It looks like you are using an optional state variable with a binding. You get an error because TextField's initializer expects a Binding<String> rather than a Binding<String?>. I guess you could solve the problem by adding another initializer that accepts Binding<String?>, or maybe making an adapter that converts between Binding<String?> and Binding<String>, but a better option might be to have a good think about why you need your state variable to be optional in the first place. After all this string something that will be displayed in your UI -- what do you expect your TextField to display if storedValue is nil?

Related

SwiftUI: .onOpenURL action closure is not updating #State property which is of type URL

I am implementing my first iOS Application with SwiftUI, in which I want users to be able of clicking on an invitation link for joining a topic group (DeepLinking).
Like joining a WhatsApp-Group with a link.
Therefore I associated my Domain (lets say: https://invite.example.com/) with my Swift-Project.
Whenever I click/open a URL (e.g. https://invite.example.com/313bceff-58e7-40ae-a1bd-b67be466ef72) my app opens and if the user is logged in an the .onOpenURL action method is triggered as expected.
However, if I try to save the url in a #State URL property in the called closure, it gets not stored.
The #State boolean property for showing the sheet is set to true though.
That is my code in the #main struct.
import SwiftUI
#main
struct MyApp: App {
#StateObject private var appRouter: AppRouter = AppRouter()
#State private var openAcceptInvitationSheet: Bool = false
#State private var invitationURL: URL? = nil
var body: some Scene {
WindowGroup {
switch appRouter.currentScreen {
case .launch:
EmptyView()
case .login:
LoginSignupNavigationView()
case let .home(authenticatedUser):
HomeTabView()
.environmentObject(authenticatedUser)
.onOpenURL { url in
invitationURL = url //Gets not set -> url is not nil here!
openAcceptInvitationSheet = true //Is working and sheet will be presented
}
.sheet(isPresented: $openAcceptInvitationSheet) {
//invitationURL is nil -> Why?
AcceptInvitationNavigationView(invitationURL: invitationURL!)
}
}
}
}
}
Everything else is working here as expected. I guess I have a misconception of how the #State properties work. However in all my other views I managed assigning values to #State properties in closures which later can be used.
Rather than using two variables for your sheet, use one – the optional URL.
.sheet(item: $invitationURL) { url in
AcceptInvitationNavigationView(invitationURL: url)
}
The optionality of your URL? state variable takes the place of the boolean value in determining whether the sheet should display, and the sheet receives the unwrapped URL value.
I don't think that your URL is not being set – it's more a question of it's not set at the time the original sheet's closure is evaluated, which is a subtly different SwiftUI object life cycle thing! Sticking to a single object massively simplifies everything. You'll also be able to change your code in AcceptInvitationNavigationView to expect a URL rather than having to deal with being passed an optional URL.
As noted in comments, this only works if URL conforms to Identifiable, which it doesn't by default. But you can use a URL's hashValue to synthesize a unique identifier:
extension URL: Identifiable {
var id: Int { hashValue }
}

How to convert Binding<String?> to Binding<String> in SwiftUI

How to convert Binding<String?> to Binding in SwiftUI
variable declared as:
#Binding var name: String?
It depends where do you use it, but most probably, you'd need something, like
Binding(name) ?? .constant("")
In some cases it might be needed proxy binding created in place, like in https://stackoverflow.com/a/59274498/12299030.
For scenarios with arguments you can use approach like in https://stackoverflow.com/a/62217832/12299030.
You can create your own Binding Extension with default values for cases in where Value is Optional.
Swift 5 Extension
extension Binding where Value: Equatable {
/// Given a binding to an optional value, creates a non-optional binding that projects
/// the unwrapped value. If the given optional binding contains `nil`, then the supplied
/// value is assigned to it before the projected binding is generated.
///
/// This allows for one-line use of optional bindings, which is very useful for CoreData types
/// which are non-optional in the model schema but which are still declared nullable and may
/// be nil at runtime before an initial value has been set.
///
/// class Thing: NSManagedObject {
/// #NSManaged var name: String?
/// }
/// struct MyView: View {
/// #State var thing = Thing(name: "Bob")
/// var body: some View {
/// TextField("Name", text: .bind($thing.name, ""))
/// }
/// }
///
/// - note: From experimentation, it seems that a binding created from an `#State` variable
/// is not immediately 'writable'. There is seemingly some work done by SwiftUI following the render pass
/// to make newly-created or assigned bindings modifiable, so simply assigning to
/// `source.wrappedValue` inside `init` is not likely to have any effect. The implementation
/// has been designed to work around this (we don't assume that we can unsafely-unwrap even after
/// assigning a non-`nil` value), but a side-effect is that if the binding is never written to outside of
/// the getter, then there is no guarantee that the underlying value will become non-`nil`.
#available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
static func bindOptional(_ source: Binding<Value?>, _ defaultValue: Value) -> Binding<Value> {
self.init(get: {
source.wrappedValue ?? defaultValue
}, set: {
source.wrappedValue = ($0 as? String) == "" ? nil : $0
})
}
}
Usage
struct ContentView: View { 
#Binding var title: String?
var body: some View {
TextField("Title Value", text: .bindOptional($title, "Default Title"))
}
}

SwiftUI not all strings are localized

SwiftUI didn't translate the string in variable.
I have added:
"Name" = "姓名";
if I wrote:
Text("Name")
works good. I can see the label with 姓名.
If I define a variable like:
#State var title = "Name"
Text(title)
Then the localization doesn't work. Still in Chinese. Any tips?
You have to explicitly indicate that your title variable is a LocalizedStringKey, not a String.
#State var title: LocalizedStringKey = "Name"

SwiftUI Localisation not working with #State Strings

I have implemented Localisation in my SwiftUI app. Everything works fine but I'm having issues with localising #State var. Localisation is not working and I'm getting only the keys printed out. Any idea how to fix this issue?
The value of type is already in my Localizable.strings
#State var type: String
var body: some View {
VStack {
Text(self.type) // not working
Text("test") // working
}
}
You can take convert the string into a NSLocalizedString
Text(NSLocalizedString(type, comment: ""))
or change the type of type into a LocalizedStringKey
#State var type: LocalizedStringKey
When a string literal is passed to Text its type needs to be inferred (Since it isn't explicitly stated). Literal text is probably a fixed part of your UI, so it is interpreted as a LocalizedStringKey.
When you pass the property self.type, it has an explicit type - String, so the Text(_ verbatim:) initialiser is used resulting in non-localised text.
If you want that property to be localised you can use the LocalizedStringKey(_ string: String) initialiser:
Text(LocalizedStringKey(self.type))

How can I unwrap an optional value inside a binding in Swift?

I'm building an app using SwiftUI and would like a way to convert a Binding<Value?> to a Binding<Value>.
In my app I have an AvatarView which knows how to render an image for a particular user.
struct AvatarView: View {
#Binding var userData: UserData
...
}
My app holds a ContentView that owns two bindings: a dictionary of users by id, and the id of the user whose avatar we should be showing.
struct ContentView: View {
#State var userById: Dictionary<Int, UserData>
#State var activeUserId: Int
var body: some View {
AvatarView(userData: $userById[activeUserId])
}
}
Problem: the above code doesn't combine because $userById[activeUserId] is of type Binding<UserData?> and AvatarView takes in a Binding<UserData>.
Things I tried...
$userById[activeUserId]! doesn't work because it's trying to unwrap a Binding<UserData?>. You can only unwrap an Optional, not a Binding<Optional>.
$(userById[activeUserId]!) doesn't work for reasons that I don't yet understand, but I think something about $ is resolved at compile time so you can't seem to prefix arbitrary expressions with $.
You can use this initialiser, which seems to handle this exact case - converting Binding<T?> to Binding<T>?:
var body: some View {
AvatarView(userData: Binding($userById[activeUserId])!)
}
I have used ! to force unwrap, just like in your attempts, but you could unwrap the nil however you want. The expression Binding($userById[activeUserId]) is of type Binding<UserData>?.

Resources