SwiftUI Localisation not working with #State Strings - ios

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))

Related

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

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?

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 }
}

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"

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>?.

Localized String with Interface Builder User Defined Runtime Attributes

I am currently trying to create a localized accessibilityLabel in the storyboard (I am trying to avoid doing it programatically). It seems that whenever I use the Localized String option, the accessibilityLabels ends up being set to the localized string key that I have provided rather than the string itself. Does anyone have the solution to this problem? Any help would be greatly appreciated.
I guess you expect the localized string to be taken from Localizable.strings. The "Localized String" type doesn't work this way, it's just a marker to indicate that the value of the user defined runtime attribute will participate in the localization process when you use base localization. Please take a look at https://stackoverflow.com/a/24527990/2876231 for a lengthier explanation.
The attribute type needs to be Localizable String, and then you'd translate it in the .strings file using the following property:
"KLc-fp-ZVK.ibExternalUserDefinedRuntimeAttributesLocalizableStrings[0]" = "¡Hola!";
Unfortunately, it doesn't seem to work with a named attribute, but only with the long property above.
(Based on Andrew's answer here: Localize a view within a storyboard using "User Defined Runtime Attributes")
I did the customization of an attribute with the simple solution of localizing the attribute by code:
private struct AssociatedKeys {
static var someTagKey = "someTag"
}
#IBInspectable var someTag: String? {
get {
return NSLocalizedString(
objc_getAssociatedObject(self, &AssociatedKeys.someTagsKey) as? String ?? "", comment: "")
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.someTagsKey,
newValue as NSString?,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
And after that you can easily extract all the strings from the xib and storyboard files with an egrep:
egrep -ohr --include="*.xib" --include="*.storyboard" '<userDefinedRuntimeAttribute type="string" keyPath="someTag" value="[^"]+"/>' . >> extracted-strings.txt
And after that eliminate the tags in the strings file by the following sed and then you have to plain strings file for xcode ready:
sed -i -e 's/^<userDefinedRuntimeAttribute type="string" keyPath="someTag" value=\("[^"]*"\)\/>/\1 = \1;/g' extracted-strings.txt

Resources