SwiftUI Localization - swiftui

I am facing localisation issue in SwiftUI. Issue is happening when translation has placeholder. I am getting error "Instance method 'appendInterpolation' requires that 'LocalizedStringKey' conform to '_FormatSpecifiable'"
Code
struct Translation {
struct school{
static var location: LocalizedStringKey {
return "schoolLocation %#"
}
}
}
Translation file
"schoolLocation %#" = "My school location is %#";
SwiftUI view
var location = "Some Name"
.navigationBarTitle("\(Translation.school.location) \(location)")
Please help me if i am doing something wrong.

What you are doing is you are giving back an already interpolated string with %# to an interpolated string. So the string that you are generating looks like this: "schoolLocation %# Some Name". You can do it this way:
struct Translation {
struct school{
static func location(name: String): LocalizedStringKey {
return "schoolLocation \(name)"
}
}
}
And then you can use your translation like this:
var location = "Some Name"
.navigationBarTitle(Translation.school.location(name: location))

Related

How to run a function from switch statement in swiftui?

I trying to run a function with parameter from a switch statement in swiftui but kept getting the "Type '()' cannot conform to 'View'" error. I think the switch statement and the function should be correct. No matter how I play around with the case statement, I'll still get the same error message.
struct questionsData: Codable {
enum CodingKeys: CodingKey {
case question
case answers
case correctAnswerIndex
}
//var id = UUID()
var question: String
var answers = [String]()
var correctAnswerIndex: Int
}
struct ThemeView: View {
var quizzes = [questionsData]()
let themeName: String
var body: some View {
let themeselected: String = themeName
var jsonfile: String = ""
switch themeselected {
case "Money Accepted":
jsonfile = "Accounts"
return loadQuizData(jsonname: jsonfile)
case "Computers":
jsonfile = "Computers"
return loadQuizData(jsonname: jsonfile)
default:
Text("invalid")
}
}
func loadQuizData(jsonname: String){
guard let url = Bundle.main.url(forResource: jsonname, withExtension: "json")
else {
print("Json file not found")
return
}
let data = try? Data(contentsOf: url)
var quizzes = try? JSONDecoder().decode([questionsData].self, from: data!)
quizzes = quizzes!
}
}
struct ContentView: View {
#State private var selection: String?
let quizList = ["Money Accepted","Computers","Making an appointment", "Late again", "Shopping", "Renting a place", "Accounts", "Letter Writing", "Planning a business", "Business Expression 1", "Business Expression 2", "How to ask the way"]
var body: some View {
NavigationView{
List(quizList, id:\.self) { quizList in
NavigationLink(destination: ThemeView(themeName: quizList)){
Text(quizList)
}
}
.navigationTitle("Select quiz theme")
}
}
}
Please kindly assist... still new to swiftui.
Greatly appreaciated.
I don't get exactly how your ThemeView should look like, maybe you can show us a preview. But there are some mistakes in there. Firstly, try to extract that logic in a ViewModel. Basically have a separate layer to handle business logic such as json parsing and other stuff. Secondly, try not to have var in views, unless they are marked as #State, #Binding, #ObservedObject... . Not least, SwiftUI views should create views not handle logic such as ViewControllers in UIKit, your switch create no view this is why it does not conform to View.

Document based app shows 2 back chevrons on iPad

I did a small sample application to show my problem. I used the multi-platform document app template that Xcode 14.0.1 offers, creating my own package file format for this.
I want to create a document based app running on macOS and on iPad.
When running on macOS, everything works as expected.
On the iPad, when opening the app, the file chooser opens.
On opening an existing or creating a new file, the screen looks like this:
The left chevron does nothing, while the right chevron shows the document chooser again.
What's the left, ever so slightly larger chevron on the left doing here and how can I get of it? Is this an error with the framework that should be reported to Apple?
PS don't get distracted by the name of this sample app–the real app will need some navigation and I first thought the 2nd chevron show up cause of this–in the sample I built for this post, there is no navigation though. So this 2nd chevron seems to be a "built in" issue...
For the sake of completeness, here's my code:
import SwiftUI
#main
struct so_DocumentAppWithNavigationShowsMultipleChevronsApp: App {
var body: some Scene {
DocumentGroup(newDocument: so_DocumentAppWithNavigationShowsMultipleChevronsDocument()) { file in
ContentView(document: file.$document)
}
}
}
import UniformTypeIdentifiers
extension UTType {
static var appfroschFile: UTType {
UTType(importedAs: "ch.appfros.so-DocumentAppWithNavigationShowsMultipleChevrons")
}
}
struct so_DocumentAppWithNavigationShowsMultipleChevronsDocument: FileDocument {
var document: Document
init(document: Document = Document(text: "Hello, world!")) {
self.document = document
}
static var readableContentTypes: [UTType] { [.appfroschFile] }
init(configuration: ReadConfiguration) throws {
guard let fileWrappers = configuration.file.fileWrappers
else {
throw CocoaError(.fileReadCorruptFile)
}
guard let documentFileWrapper = fileWrappers["document"],
let data = documentFileWrapper.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
document = try JSONDecoder().decode(Document.self, from: data)
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = try JSONEncoder().encode(document)
let documentFileWrapper = FileWrapper(regularFileWithContents: data)
let mainFileWrapper = FileWrapper(directoryWithFileWrappers: [
"document": documentFileWrapper
])
return mainFileWrapper
}
}
struct Document: Codable {
var text: String
}
struct ContentView: View {
#Binding var document: so_DocumentAppWithNavigationShowsMultipleChevronsDocument
var body: some View {
TextEditor(text: $document.document.text)
}
}
Can see the same problem with the default Document based app when using Xcode 14.1 (14B47) running on the iPad simulator with iOS 16.1. So definitely a bug (and worth reporting to A as such).
At a guess, the second, non-functional back button is what would have been the back button for navigating in SideBar. And the logic to not display when working on a document is what has been broken.
Fortunately simple workaround for the bug is to explicitly specify toolbar's role using the toolbarRole modifier, e.g.
#main
struct so_DocumentAppWithNavigationShowsMultipleChevronsApp: App {
var body: some Scene {
DocumentGroup(newDocument: so_DocumentAppWithNavigationShowsMultipleChevronsDocument()) { file in
ContentView(document: file.$document)
.toolbarRole(.navigationStack) // <-- Specifying this gets rid of double chevron on iOS
}
}
}

Dynamically Combining Currency Symbol to TextField Text

Is it possible to dynamically combine a currency symbol to some text before displaying it via TextField on a single text line? Currently my currency symbol is on the line above the TextField text "Enter Amount > "
This code produces the errors: "Cannot assign to property: 'self' is immutable" and "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols."
I would like to use something like this in a form.
struct ContentView: View {
#State private var moneyS: String = ""
var curr: String = ""
var curSymb: Int = 2
var mxx: String = ""
var body: some View {
Text("Blah Blah Blah")
switch curSymb {
case 1:
curr = "$"
case 2:
curr = "€"
default:
curr = "£"
}
mxx = "Enter Amount > " + curr
TextField(mxx, text: $moneyS)
.keyboardType(.decimalPad)
}
}
Yes, it certainly is possible. By default, it uses VStack-like layout and places all the views vertically. We can use HStack (horizontal stack) to align your text views horizontally.
Here is the updated version:
struct ContentView: View {
...
var curr: String {
switch curSymb {
case 1: return "$"
case 2: return "€"
default: return "£"
}
}
var body: some View {
HStack(alignment: .firstTextBaseline) { // note that we use firstTextBaseline in order to keep the text aligned even if you decided to have different font for every part of the text
Text("Blah Blah Blah")
TextField("Enter Amount > " + curr, text: $moneyS)
.keyboardType(.decimalPad)
}
}
}
Also, note, that I have also moved the curr calculation to a variable so that the body code stays small.
Thanks for the assistance. I like the concise compact code.
Sorry for not being clear about the blah blah blah line. There are other lines of text included in a VStack but just "Enter Amount > " and the currency symbol in the HStack.
.currency(code: "INR")
You can set country code dynamically.
struct ExchangeRateView: View {
#State var currencyFromInput:Double = 0.1
var body: some View {
TextField("Enter Amount", value: $currencyFromInput, format: .currency(code: "INR"))
}
}

Are there better alternatives to :Any for a swiftui struct value?

I'm trying to implement an app where the response from a server call is used to build a view. The view can be one of 5 or 6 different types, depending on the data that comes back, all of which have different requirements for the shape and type of the data that gets passed in to them. Whats the best way to define the struct/class for the incoming data? The only way I've been able to make it work so far is by using :Any as the data type
This is the broad strokes of what I'm trying to do...
struct PageViewData {
type: String
id: Int
viewData: Any
}
struct MyViewA: View {
type: String
id: Int
viewData: MyViewADataShape
var body: some View {
//contents here
}
}
struct MyViewADataShape {
navigation: [NavigationItem]
cta: String
}
struct MyViewB: View {
type: String
id: Int
viewData: MyViewBDataShape
var body: some View {
//contents here
}
}
struct MyViewBDataShape {
pageTitle: String
author: String
wordCount: Int
}
var serverResponse: PageViewData = fetchDataFromServer()
if(serverResponse.type == "A") {
MyViewA(serverResponse)
}
if(serverResponse.type == "B") {
MyViewB(serverResponse)
}
As Yonat mentioned, an enum works well here:
struct PageView: View {
enum Response {
case something(DecodableResponseA)
case orOther(DecodableResponseB)
}
// if you were using id + type to determine what your response was, they are
// unnecessary now, but only you know what you were using them for
let id: Int
let response: Response
var body: some View {
self.viewForResponse(response)
}
private func viewForResponse(_ response: Response) -> some View {
switch response {
case .something(let somethingResponse): return AnyView(SomethingView())
case .orOther(let orOtherResponse): return AnyView(OtherView())
}
}
}

SwiftUI Optional TextField

Can SwiftUI Text Fields work with optional Bindings? Currently this code:
struct SOTestView : View {
#State var test: String? = "Test"
var body: some View {
TextField($test)
}
}
produces the following error:
Cannot convert value of type 'Binding< String?>' to expected argument type 'Binding< String>'
Is there any way around this? Using Optionals in data models is a very common pattern - in fact it's the default in Core Data so it seems strange that SwiftUI wouldn't support them
You can add this operator overload, then it works as naturally as if it wasn't a Binding.
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
This creates a Binding that returns the left side of the operator's value if it's not nil, otherwise it returns the default value from the right side.
When setting it only sets lhs value, and ignores anything to do with the right hand side.
It can be used like this:
TextField("", text: $test ?? "default value")
Ultimately the API doesn't allow this - but there is a very simple and versatile workaround:
extension Optional where Wrapped == String {
var _bound: String? {
get {
return self
}
set {
self = newValue
}
}
public var bound: String {
get {
return _bound ?? ""
}
set {
_bound = newValue.isEmpty ? nil : newValue
}
}
}
This allows you to keep the optional while making it compatible with Bindings:
TextField($test.bound)
True, at the moment TextField in SwiftUI can only be bound to String variables, not String?.
But you can always define your own Binding like so:
import SwiftUI
struct SOTest: View {
#State var text: String?
var textBinding: Binding<String> {
Binding<String>(
get: {
return self.text ?? ""
},
set: { newString in
self.text = newString
})
}
var body: some View {
TextField("Enter a string", text: textBinding)
}
}
Basically, you bind the TextField text value to this new Binding<String> binding, and the binding redirects it to your String? #State variable.
I prefer the answer provided by #Jonathon. as it is simple and elegant and provides the coder with an insitu base case when the Optional is .none (= nil) and not .some.
However I feel it is worth adding in my two cents here. I learned this technique from reading Jim Dovey's blog on SwiftUI Bindings with Core Data. Its essentially the same answer provided by #Jonathon. but does include a nice pattern that can be replicated for a number of different data types.
First create an extension on Binding
public extension Binding where Value: Equatable {
init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) {
self.init(
get: { source.wrappedValue ?? nilProxy },
set: { newValue in
if newValue == nilProxy { source.wrappedValue = nil }
else { source.wrappedValue = newValue }
}
)
}
}
Then use in your code like this...
TextField("", text: Binding($test, replacingNilWith: String()))
or
TextField("", text: Binding($test, replacingNilWith: ""))
Try this works for me with reusable function
#State private var name: String? = nil
private func optionalBinding<T>(val: Binding<T?>, defaultVal: T)-> Binding<T>{
Binding<T>(
get: {
return val.wrappedValue ?? defaultVal
},
set: { newVal in
val.wrappedValue = newVal
}
)
}
// Usage
TextField("", text: optionalBinding(val: $name, defaultVal: ""))