SwiftUI, Programming Peter Ent SwiftUI, Programming Peter Ent

Localizing your SwiftUI App

Tips on making your app available in other languages.

“Localization” is the process of making your app usable in multiple languages. This is different from “internationalization” which is taking localization to the next level where you handle colors and icons in a meaningful way to the locale of the app’s user. This also includes being sensitive to the format of currency, numbers, and dates and times. Fortunately, iOS does some of this for you - if you have used the right APIs. For example, if you create a Date() and then display it using DateFormatter with DateStyle.short, iOS will see to it that the date reads correctly in the device locale (eg, 7/28/2020 in the United States and 28/7/2020 anywhere else in the world).

Xcode isn’t exactly as friendly as Android Studio when it comes to making your English-only (hard-coded strings) code ready for localization, so my post here shows you one way to handle this. There is a program called genstrings that can help, but I am not covering that here.

It is helpful to pick a second language to start with. You can use British English but the differences aren’t as obvious as say using Dutch, so I will go with that.

Project Step

You will need a file that contains the original - English for myself - strings you want to be available in other languages.

Use Xcode’s File->New->File menu command and in the template selector, pick Strings File in the Resources section. Name the file Localizable.strings for this example (it can be any name you want). The file will be empty; you will add content to it below.

Screen Shot 2020-07-28 at 2.41.50 PM.png

String Extension

Next make an extension to the String class to make some usages easier, which will make more sense a bit later in this post. For now, create a new Swift file called Strings+Extensions.swift and put this into it:

extension String {
func localized() -> String {
return NSLocalizedString(self, comment: self)
}

}

Extraction Step

Open the English version of Localizable.strings and put it into is own Window (or vertically split the Xcode editor so that the strings file is in one half the other half you can use for source code). Open one of your files that contains hard-coded strings. For example:

Button(“Close”) { … }

In the Localizable.strings file, add the line:

”close-button” = “Close”;

Note that both sides of the equal sign (=) are in double-quotes and the line ends with a semi-colon (;). This format is very important and Xcode will flag it as an error if the formatting is not correct.

Back in the source code, replace ”Close” with ”close-button” which is what is on the left side of the equal sign in the Localizable.strings file.

Button(“close-button”) { … }

I use identifiers for the string key codes (the left side) rather than the English text, but you could also have used ”Close” = “Close” in the localization strings file. Some people prefer the source code to read with the original strings. I find using codes makes it easier to spot missing translations.

Let’s say you have this in your code:

var defaultAddress = “Unknown Street”

You add ”default-address” = “Unknown Street” to the Localizable.strings file and then replace the code:

var defaultAddress = “unknown-street”

When you run the app, the default address will be “unknown-street”! What happened to the localization? The Button shows up as “Close” and not “close-button”.

This is because most SwiftUI components, like Button and Text not only take String arguments but LocalizedStringKey arguments as well. In other words, when you pass ”close-button” to the Button initializer, SwiftUI looks to see if that string is a localization key and if so, uses what it finds in the Localizable.strings file; if not, it uses the string as passed.

In the case of defaultAddress, you want to initialize it like this:

var defaultAddress = “unknown-street”.localized()

That String extension I added above comes in handy to quickly add code to programmatically switch to the localized version. The down-side is that if you use a code like I do, if you forget to put that code (or misspell it) into the Localizable.strings file, the app will display the code at runtime. Which is why a lot of people use the English string as their code and not the way I do it. But that’s just my preference.

Localize

Now that you have all of the English code/value pairs in the Localizable.strings file, it’s time to prepare it for translation. In the Project Navigator, select the Localizable.strings file, then open the File Inspector (⌥+⌘+1). Tap the Localize button.

Screen Shot 2020-07-28 at 12.33.35 PM.png

This will create the English localization for this file and create a directory in your project called en.lproj (you can see it with Finder).

Now open your project file from the Project Navigator (make sure your project is selected and not the target) and open the Localizations section of the Info tab. You will see English - Development Language.

Screen Shot 2020-07-28 at 12.34.21 PM.png

Tap on the Plus (+) button and pick your languages. I picked Dutch.

A dialog opens for you to select the resources to localize. For SwiftUI you can uncheck LaunchScreen.storyboard and just localize Localizable.strings.

Screen Shot 2020-07-28 at 12.35.05 PM.png

Once you have done that, look at the Project Navigator and you will see the Localizable.strings entry is now folder with the different language files in them.

Screen Shot 2020-07-28 at 2.35.10 PM.png


Translation Step

Unless you know people who speak the languages you are going to use (or you a native speaker - yeah for you!), you will find Google Translate to be your new best friend.

What I did was open Google Translate in new browser window and set it aside. Then I open the Localizable.strings file for the language I’m working on. Let’s say Dutch in this case.

If you did the Localizable.strings file for English before creating new localizations like I suggest in the steps above, Xcode will copy all of the English strings into the Dutch file for you - saving you a lot of time.

Now all you need to do is copy the English phrase to the Google Translate window where it will automatically get translated, then copy it back and paste it into your Localizable.strings file for that language.

Screen Shot 2020-07-28 at 2.56.20 PM.png

Go ahead and translate all of the strings.

As good as Google Translate it, it may not be perfect. It is best to get someone who knows the target language well to proof read your app. Sometimes the context will change which word to use and you do not want to offend anyone!

Testing it Out

First, run your app in a simulator to make sure you do not see any string code keys (eg close-button) showing up. If you do, make sure you put the correct key in the Swift code and it is spelled correctly (“closed-button” is different from “close-button” as is “Close-Button”).

Now you want to make sure the localization works and it’s pretty easy to do that.

In Xcode, edit the current schema (you can also create a new one, even one per language):

Screen Shot 2020-07-28 at 3.02.26 PM.png

In the schema editor, change the Application Language and Application Region drop-downs. Here I’ve changed them to “Dutch” and “Netherlands”.

Screen Shot 2020-07-28 at 3.04.05 PM.png

Now run your app. When it appears in the Simulator, it will behave as if it in the Netherlands and you should see all of the strings you changed now in Dutch!

Screen Shot 2020-07-28 at 3.06.22 PM.png

One More Thing

Let’s say you are writing a SwiftUI view. For example, something that displays a product title. You might write it like this:

struct ProductTitle: View {
let label: String
let title: String
var body: some View {

        HStack {
Text(label)
Spacer()
Text(title)
}
}
}

Now you want to use this new view and pass it different labels which you have put into the Localizable.strings files:

VStack {
ProductTitle(label: “primary-label”, title: product.primary)
ProductTitle(label: “secondary-label”, title: product.subtitle)
}

When you run this, you do not get your localized text, but instead you see the code words (eg, “primary-label”). That’s weird, because the string you are just passing directly to a SwiftUI Text view so it should work, right?

No, because the Text view has multiple initializers. It has one which takes a String and one which takes a LocalizedStringKey. You passed it a String.

So you could simply do:

ProductTitle(label: “primary-label”.localized(), title: product.primary)

but that’s kind of messy. A better approach would be to change the data type of label inside of ProductTitle to:

let label: LocalizedStringKey

and now the data types match what you want. When you run your app, you will see the localized strings as you expect. This is a little more versatile in that you can also pass it a string that is not a localization key.

Summary

So there you have it. Create a Localizable.strings file with ”key-code”=“value”; pairs (do not forget the semi-colon), use Google Translate to do the translations for you, and test it out by editing the runtime schema (or create new ones).

Een fijne dag verder and have fun and take the world by storm!


Read More
SwiftUI, Programming Peter Ent SwiftUI, Programming Peter Ent

Custom Binding with SwiftUI

Use a custom binding to get more control over the data flow.

A couple of days ago I was looking at some SwiftUI questions and saw one from a fellow trying to store the value of a picker into some Core Data. I thought I would share my solution here. Two caveats:

  1. This is for SwiftUI 1.0 - the person asking the question was using SwiftUI 2.0 (in beta as of this writing), but I think my solution will still work.

  2. This solution is not something uniquely mine - you will see other people mention it in their posts and writing. I thought I would highlight it specifically.

Here’s the problem: you want to use a component (DatePicker in this case) which transfers the value selected by the user via a Binding. If you are coming from UIKit thinking, your instinct is to use a delegate. Very understandable but that isn’t going to work for SwiftUI. The way to use the picker is:

private @State var selectedDate = Date()

DatePicker(
selection: self.$selectedDate,
displayedComponents: [.date]) {

EmptyView()
}.labelsHidden()

When the user changes any of the wheels of the DatePicker the value of selectedDate is immediately changed.

The question asked was: How can the value (selectedDate) be stored into Core Data? More generically, how do you react to this choice made by the user?

If you wanted the UI to change when the user picked a date, you can set that up easily using an IF statement and SwiftUI will detect that selectedDate changed, run the body code again, execute your IF statement, and produce a new View which it with then use to change the screen. That’s pretty straightforward. But what if you want to do something non-UI with the value when its changed?

The solution is to use a custom binding. Let’s keep selectedDate as the backing store variable so it can be used elsewhere and add a new var which reacts to the DatePicker changing its selection in a custom way:

private var handleBinding: Binding<Date> {
Binding(
get: { self.selectedDate },
set: { (newValue) in
self.selectedDate = newValue
// do something with newValue, like call another function
// or store it with Core Data or fetch some remote data

})}

and then put this to use:

DatePicker(
selection: self.handleBinding,
displayedComponents: [.date]) {

EmptyView()
}.labelsHidden()

The @State variable that was used in the binding with the DatePicker is replaced with the custom binding, handleBinding. It works exactly the same, except when the DatePicker’s selected date changes, the handleBinding set function is called, setting the @State variable and then doing whatever else you want it to do.

You can of course, use custom binding with your own components. If you component has a @Binding var in it, you can pass a custom binding. This will give you more control over the flow of data in your SwiftUI app, just don’t abuse it and write sloppy code. Use it only when you need to and try to keep your code clean and readable.

Read More
SwiftUI, Programming Peter Ent SwiftUI, Programming Peter Ent

A Year of SwiftUI

At WWDC 2019 (that’s last year), Apple introduced an amazing new UI framework - SwiftUI. Its introduction was (for me at least) on par with the announcement of Swift itself. I’d like to take you through my adventure with SwiftUI - version 1 - to show what you’ve missed and to hopefully, prepare you for Swift 2.0

Reactions

I had recently been working with React Native and was planning to re-write the app for iOS. This was well after WWDC 2019, so I had already heard of SwiftUI. My work with React Native was important in that I was already in the “reactive” frame of mind. That is, I worked with a UI whose display was state driven and bound tightly with the state. If you aren’t familiar with reactive programming here is a quick snapshot:

Your UI is composed of controls whose content (or appearance) is tied to a value in the component’s state. For example, a label whose’s text property is the value of state.amount. Whenever you change amount in the state, the UI automatically refreshes with the updated value. In other words, you do not programmatically assign the label an updated value; instead, the label draws its value from the state. Or, the UI reacts to changes in the state. Clever, huh?

Having been thinking along those lines, I started looking into SwiftUI. Imagine my surprise at seeing @State and talk about the UI reacting to changes in the state. Apple had gone done their own version of reactive programming! And it wasn’t just a little nod toward it - it was full-fledged deep end.

SwiftUI is only part of the new offering. Along with updates to Swift itself (which are crucial to making SwiftUI syntax so easy) is the Combine framework. Combine is what allows @State to work and enables the Observer pattern to be so easily incorporated into your apps.

So this was going to be the new way forward to develop iOS apps. OK, that’s what I was going to do with this React Native re-written app: SwiftUI 100%.

There were a lot of bumps and challenges along the way. And you can read challenges as hair-pulling and doubt as to whether this was the right decision or not.

First off, I knew SwiftUI did not fully replace UIKit. I knew I had to write integration code - Apple’s own tutorials to get you started with SwiftUI did that. For example, the app would need to use MapKit and MKMapView was not a SwiftUI element. Fortunately Apple was prepared for this and provided a way to wrap UIKit components so that they would appear to be SwiftUI elements. Pretty neat actually.

Secondly, I had to really look at problems differently. How do I set up the UI and its state so that when values change the right parts of the UI change? How do I receive new values from the remote service and get the UI to reflect those new values? How I put up a date chooser? How can I make scrolling through a 2000 element list faster? The challenges just kept coming.

A Model Year

You may be familiar with the “massive view controller” problem. This is where you pack everything and the kitchen sink into a single UIViewController file. You put access to your data, manipulations for that data, formatting that data, etc. all into the file whose real job is to get the UIView to display. It is easy to fall prey to this, especially as you are flushing out ideas or doing prototyping.

And this exactly what I started do with SwiftUI. I wasn’t concerned at first with separation of concerns - I was more interested in understanding how SwiftUI worked. But in forgoing this design pattern, I actually missed a key part of how SwiftUI works. What happened was that I was trying to get the screen to see changes the code was making, but they weren’t showing up.

I took a step back and realized that I was neglecting a very powerful concept in SwiftUI - the model. It’s pretty simple: you put your data into a class that has functions to manipulate it and then make the UI observe changes to it.

SwiftUI provides ObservableObject for this purpose; it’s part of Combine. When you make a class Observable, you can select properties to be @Published which sets up the observer pattern and all the code needed to watch for changes.

I pulled a lot of code out the UI classes and into models. I wound up with lots of models - essentially one per “screen” with some go-betweens. This allowed me to just set up the binding between the UI and the data in the model, have code - like remote server call handlers - update the appropriate models which would then automatically trigger the UI to change. Voila - an app!

Summary

As often as I questioned my decision to use SwiftUI, I just as often determined it was the right path. Apple was going to make this its de facto way to write apps. If you wanted to be in the lead position and wanted to take advantage any new stuff, you needed to be with SwiftUI.

And look what happed at WWDC 2020: Widgets and App Clips. You write those in SwiftUI. And - maybe even more important - cross device deployment. Your app can run on an Apple Watch, iPhone, iPad, Apple TV, and macOS - one app. SwiftUI’s runtime makes sure your app looks the best for the device and behaves appropriately. I’m sure there’s some tweaking you do in the app to actually make that happen, but Apple’s done all the heavy lifting for you - IF you use SwiftUI.

A lot of folks have been afraid to take the plunge, citing the old “SwiftUI is 1.0 and will change or is buggy.” Yeah that was true. So now you’ve got SwiftUI 2.0 on the horizon - do you want to be a leader or do you want to hang back and eat dust?

Read More
SwiftUI Peter Ent SwiftUI Peter Ent

Collapsable Section Headers in SwiftUI

Have a SwiftUI list with a lot of items? Break it into sections and make each section collapsable. Read on….

Let’s say you have this SwiftUI List with a bunch of different sections. It might be nice if users could tap on the section header and have that section collapse. Its actually pretty easy to do this, you just need a few parts.

Model

Start with a model, which we’ll call SectionModel. It is an ObservableObject because you want SwiftUI to recognize when it changes and redraw the UI. The model’s job is to know which sections are open and which are not.

// 1
class SectionModel: NSObject, ObservableObject {
    // 2
    @Published var sections: [String:Bool] = [String:Bool]()

    func isOpen(title: String) -> Bool {
        // 3
        if let value = sections[title] {
            return value
        } else {
            return true
        }
    }

    // 4
    func toggle(title: String) {
        let current = sections[title] ?? true
        withAnimation {
            sections[title] = !current
        }
    }
}
  1. Declare the SectionModel to implement the Observable protocol. This will allow it to be a @ObservedObject later.

  2. The sections dictionary holds the Bool to say whether or not the section is open (true) or closed (false). This is marked to be @Published so SwiftUI knows it should be watched for changes.

  3. The isOpen function looks to see if a section, by its title, has a value and if so, returns it. If the section has not yet been toggled open or closed, return true - by default all sections are open. You can return false if you want the List to initially show all the sections as closed.

  4. The toggle() function simple inverts the value of the section state and again uses true as the default state.

Section Header

Now we need a custom Section header. This will be simple also: a Text, a Spacer, and an Image that shows the section open or closed.

// 1
struct CategoryHeader: View {
    var title: String
    // 2
    @ObservedObject var model: SectionModel
    var body: some View {
        HStack {
            Text(title)
            Spacer()
            // 3
            Image(systemName: model.isOpen(title: title) ?
                "chevron.down" : 
                "chevron.up")
        }
        // 4
        .contentShape(Rectangle())
        .onTapGesture {
            // 5
            self.model.toggle(title: self.title)
        }
    }
}
  1. Create the CategoryHeader as a SwiftUI View struct. We are calling this “CategoryHeader” to avoid confusion with header part of the SwiftUI Section.

  2. We are going to pass to this struct the SectionModel and mark it as an @ObservedObject so SwiftUI will notice when it changes (from the SectionModel toggle() function).

  3. The Image displayed depends on the state of the section’s “openness”.

  4. Use a contentShape of Rectangle so that the user can tap anywhere in the header to open or close it. Without contentShape the user could only tap on the title or the chevron.

  5. Adding the onTapGesture makes it possible for the user to tap the section header to open or close which is handled by calling on the model.

When the user taps the header, the model just changes the values inside its sections dictionary. Because this is an observed property (@Published inside an ObservableObject), SwiftUI will detect that change and redraw the header.

The List

Now to modify the List. The outer ForEach is going through your primary model which is (I assume) divided into the sections. So each section needs to display the header. Whether or not its content is showing depends on the value in the SectionModel. The List looks like this:

List {
    // 1
    ForEach(self.dataModel, id:\.self) { data in
       // 2
       Section(
           header: CategoryHeader(
                        title: data.title, 
                        model: self.sections)
      ) {
          // 3
          if self.sections.isOpen(title: data.title) {
              // a ForEach to render all of the items in this section
          } else {
              EmptyView()
          }
    }
}
  1. The outer ForEach is looping through your data model, extracting a single item.

  2. The SwiftUI section is given the CategoryHeader defined earlier with the title and model (which will be defined in a moment).

  3. If the section for this item is open (based on the title), the ForEach will run and display the content rows. If it is closed, the else clause displays EmptyView().

Define the sectionModel outside of this View’s var body:

@ObservedObject private var sections = SectionModel()

When you tap on the header, its onTapGesture tells the model to toggle the value of the section’s open/closed state. Because the model is ObservableObject and has a Published member which is being changed, SwiftUI will redraw the List and your section will close (or open).

You’ll see that this animates. What SwiftUI is doing is comparing the last version of the List (section is open) to the new version (section is closed) and removes just the items being hidden.

And that’s all there is to it.

Read More