A Multi-Date Picker for SwiftUI
A date picker that allows you to select more than a single day.
The information presented here is now obsolete with the introduction of iOS 16 and SwiftUI 4.0.
Many applications have the need to pick a date. But what if you have an app that requires more than one date to be selected by the user? Perhaps you are writing a hotel reservation system or an employee scheduler and need the user to select starting and ending dates or just all the days they will be working this month.
MultiDatePicker
To help facilitate that task, I’ve written the MultiDatePicker
, a handy component that can deliver a single date, a collection of dates, or a date range, to your application. You can see screen shots below.
The MultiDatePicker
is pretty easy to use. You first need to decide which selection mode you need: a single day, a collection of days, or a range. You create a @State
var to hold the value and pass that to one of the MultiDatePicker
’s constructors. When the user makes their selection your binding will be updated.
Source for
MultiDatePicker
can be found in my GitHub Library
Here are some examples:
Single Date
@State var selectedDate = Date()
MultiDatePicker(singleDay: self.$selectedDate)
In singleDay
selection mode, the MultiDatePicker
acts similarly to the SwiftUI DatePicker
component. Unlike the SwiftUI component, MultiDatePicker
can only select dates, not times.
The calendar in the control shows the month/year of the singleDay
given.
Collection of Dates
@State var anyDays = [Date]()
MultiDatePicker(anyDays: self.$anyDays)
With the anyDays
selection mode, the binding contains the user’s selected day sorted in ascending order. If the selection order matters, you should write a custom binding to intercept the changes or use the onChanged
modifier.
Tapping a day will select it and add it to the collection; tapping the day again will de-select it and remove it from the collection.
The calendar in the control will show the month/year of the first date in the anyDays
array. If the array is empty the calendar will show the current month and year.
Date Range
@State var dateRange: ClosedRange<Date>? = nil
MultiDatePicker(dateRange: self.$dateRange)
With the dateRange
selection mode, the binding is not changed until the user selects two dates (the order does not matter, the MultiDatePicker
will sort them). For example, if the user picks two dates, the binding will have that range. If the user then taps a third date, the binding reverts to nil
until the user taps a fourth date.
The calendar in the control shows the month/year of the first date in the range given. If the dateRange
is nil
the control shows the current month and year.
The Model
Making all of this work is the model: the MDPModel
. As with most SwiftUI components, the views are data driven with a model containing the data. Often you’ll have a service (a connection to a remove system or to something outside the app) that deposits information into a model which then causes the view to react to the change in the data and refresh itself with the changes.
In the case of the MultiDatePicker
, there is no service, instead the model creates the data. And the data for the model are the days of the month in the calendar displayed. My example of MultiDatePicker
shows how preparing the data up front can make writing the UI easier.
The model works from the control date - any Date
will do, as only its month and year are important. Once the control date is set, the model constructs a calendar - an array of MDPDayOfMonth
instances that represent each day. Days that are not displayed are giving a day
number of zero (these are days before the beginning of the month and after the last day of the month). As the MDPDayOfMonth
items are created, the model determines if each is selectable (see below), if one represents “today”, and what its actual Date
is so that comparisons at runtime can be efficient.
Once the days have been created they are set into a @Published
var which triggers the UI to refresh itself, displaying the calendar. The UI can then quickly examine each MDPDayOfMonth
to see how it should appear (eg, draw a circle around “today” or highlight a selected day).
More Options
In addition to the selection mode, MultiDatePicker
has other parameters, all of which have defaults.
Selectable Days
If you look at the Any Dates screen shot you can see that the weekend days are gray. These days are not selectable. The MultiDatePicker
can be told which days are eligible for selection by using the includeDays
parameter with a value of allDays
(the default), weekendsOnly
, or weekdaysOnly
(shown in the screen shot).
Excluding Days
Other options you can use with MultiDatePicker
are minDate
and maxDate
(both default to nil
). If used, any dates before minDate
or after maxDate
are not eligible for selection and shown in gray.
Jumping Ahead (or Back)
The increment (>
) and decrement (<
) controls move the calendar forward or backward one month. If the user wants to skip to a specific month and year, they can tap on the month and year (which is a button) to bring up the month/year picker, as shown in the last screen shot.
The month/year picker replaces the calendar with two wheels to select a month and a year. Once the user has done that, they tap the month/year button again and the calendar returns, showing the newly selected month/year combination.
Weather Demo for SwiftUI
A SwiftUI app to help get you started writing your iOS apps.
When you are starting out learning a programming language or system, you often go through tutorials which give you a sense of what everything is about. But then you try to code something yourself and things get fuzzy. You read about concepts and techniques and try to put them together for your own work. Its really a learn-as-go process.
I thought I’d help out a little and I created a small, but complete, SwiftUI app. The link to the GitHub repo is below. Its called “WeatherDemo” and, as the name suggests, is a weather app. I picked weather because there are a lot of weather apps so another one wouldn’t make any difference. More important though, the point of the app is straightforward but can cover a lot of topics. Being a small app its also easy to get your head around and understand the flow of the data and the lifecycle of the elements.
Here are the topics covered by WeatherDemo, in no particular order:
How to ask permission to get the device’s current location.
How to use Core Location to search for places.
How to use Core Data.
How to use URLSession with Combine to make REST API requests from openweathermap.org
How to use the Decodable protocol to digest JSON data from the remote request.
How to create custom SwiftUI components.
How to use the Size Trait Classes to handle different screen orientations.
How to create Extensions to classes.
How to do simple localization to support multiple languages.
How to use SVG images in SwiftUI apps.
The app requires iOS 14 and Xcode 12. You will also need an account with openweathermap.org (the free account is enough) in order to run the app.
You can download or clone the GitHub repository here at weatherdemo-ios.
There is an extensive README in that repository; this article is merely an introduction.
The WeatherDemo app’s structure goes like this:
Services are used to get the device location and to fetch weather information from openweathermap.org.
A controller (or model) coordinates between the services, fetching new weather information as needed. The controller is an
ObservableObject
and@Published
a set of values which are observed by the UI components. These areCombine
framework items.The
ForecastView
displays the weather data in the controller. Because of the way SwiftUI and Combine work, telling the controller to get weather info for a new location will cause theForecastView
to update.The
LocationSearchView
is asheet
that displays search history as well as search results while you type. Pick a search result and the controller fetches the weather for that location.
That’s really all there is to the app. You’ll find a folders for helper components for both the ForecastView
and the LocationSearchView
. These are custom SwiftUI components that do things like display the wind speed or a colored temperature bar or the search term field.
SwiftUI really encourages breaking your app down into small parts and composing your app out of the them. You will find that a lot but not 100% throughout the app - it gives you a chance in some places to try it out yourself.
One of the things the WeatherDemo app does is display a graphic that represents the weather (like the sun or moon in clear conditions). These images were hand drawn by me (I’m not bragging, as you’ll see, just pointing out there are no copyright issues with the images) using an SVG editor. Using SVG images in iOS app is very recent. Normally you create a graphic at three resolutions so iOS can choose the best one for the devices. As there are more and more iOS devices with different resolutions, it is much better to use scalable vector graphics. Not only do they look good across all devices, but it means there is only one image to maintain. Plus, most graphic artists can make SVG or export SVG fairly easily.
I hope you get something out of this project. I would not take WeatherDemo and hold it up as an exemplar of best practices or a template for all your endeavors. It is meant to be a bit of a hodgepodge to demonstrate techniques with a reasonable amount of coherence. With that in mind, enjoy!
SwiftUI and the Responder Chain
A nifty way to let your users travel from field to field.
While I love working with SwiftUI, there is a piece missing which I hoped Apple would address with SwiftUI 2 - the responder chain. Or more specifically, responders with respect to keyboard input. If you are unfamiliar with the concept of a responder, it’s simply how apps receive and handle events. In other words, how app components respond to events they receive. Tapping on a key in the virtual keyboard (for iOS) or a physical keyboard (for macOS) generates an event. That event must find its way to an object that will handle - or respond - to it.
The SwiftUI TextField
is woefully bereft of the ability to be assigned responder responsibility. It’s not that TextField
cannot accept input from the keyboard. It can and works just great. But what if you have a set of TextField
s such as login and password field. Normally you would tap on the login field (which causes it to become a responder and the keyboard appears), type in the user name, then press the “return” key - which is most often labeled “next” in this circumstance - and the caret jumps to the next field.
That is still not possible with SwiftUI. If you place two TextField
s on a screen, enter text into one, press “return”, nothing happens. With TextField
you can receive information once the “return” key is pressed and you can programmatically dismiss the keyboard. But you cannot get the current TextField
to resign being the responder and get the next field to become the responder. Just isn’t going to happen.
The solution is still to write a custom component using UIViewRepresentable
. That’s not too hard and I’ll show you one way in a moment. What I do not understand is why Apple has not addressed this. I can understand not providing some automatic input field chaser, but to not provide the tools to do it yourself does not seem good form. You could argue that using UIViewRepresentable
is their solution, but given what Apple have done with SwiftUI 2, seems a shame.
GitHub
I have a public GitHub repo with some code you can use and build upon. Its nothing fancy and you can muck with it as you like. It basically works like this:
It is a
UIViewRepresentable
that makes aUITextField
.It has a
Coordinator
that implementsUITextFieldDelegate
.The
Coordinator
is handling thetextFieldShouldReturn
message. It scans the surroundingUIView
hierarchy looking for the next field as specified by the current component. If it finds one, that becomes the new responder.If there is no next field, the current component resigns as the responder, effectively removing the keyboard.
To make this work, the component (cleverly called TextFieldResponder
) assigns each underlying UITextField
a unique tag, incrementing the tag numbers. The default tag is 100 for the first one, 101 for the second, and so forth. When the textFieldShouldReturn
function is called, it checks the given textField
for its tag, increments it, then finds a UIView
with that tag and makes it the responder.
You use it like this:TextFieldResponder(title: “Login”, text: self.$login)
TextFieldResponder(title: “Password”, text: self.$password)
.isSecure(true)
As with SwiftUI TextField
, the title
becomes the placeholder and the text
is a binding that gets changed as the user types. There is also a trailing closure you can implement which is called when the “return” key is tapped.
You can find my GitHub repository at: peterent/TextFieldResponder
Modifiers
You can see in the above example, the Password field is being made secure by the presence of the .isSecure(true)
modifier. This is not actually a real modifier as UIViewRepresentable
does not support SwiftUI modifiers. This is really an extension function that returns self
. There are few more I added:
keyboardType
to change which keyboard to use for the field;returnKeyType
to change the appearance of the “return” key;autocapitalization
to change how capitalization works;autocorrection
to turn auto correct on or off.
Just use them as you would any modifier.
TextFieldResponder(title: "Login", text: self.$login)
.keyboardType(.emailAddress)
.autocorrection(.yes)
.autocapitalization(.none)
Final Thoughts
This is not a perfect solution, but if you have a bunch of input fields in your app and you would like the user to quickly move between them without having to leave the keyboard and go tap on this, this is a way to do it.
SwiftUI 2.0 - First Days
A short tour of SwiftUI 2.0
I’ve spent the last couple of weeks or so looking into SwiftUI 2.0 and wanted to document my thoughts about it while it was relatively fresh. Its often good to go back and look at early impressions of things to see how your thoughts or opinions changed.
I’m pretty happy with SwiftUI 2.0 - Apple added some new “native” components and fixed some things that I had developed work-arounds for; now those are obsolete. I looked through a list of the new features and created a simple App to learn about each new thing. Then I spent time upgrading my two apps (Journal and Password) to be SwiftUI 2.0 compliant; as of this writing I have not yet released them to the Apple App Store.
Impressions
This is a brief description of some of the new components available in SwiftUI 2.0 and how I made use of them.
Maps
The idea of a native Map component was something I was looking forward to using. I created my own MapView
for SwiftUI 1.0 using UIViewRepresentable
. With SwiftUI 2.0 the expectation was that I could toss that aside.
Alas, not so quick. While SwiftUI 2.0 comes with a nice implementation of MKMapView
, it lacks interactivity that I need. You can place annotations (markers or pins) on the map, but they are not tappable. And there does not seem to be a way to get the user’s current location to indicate or track.
I decided to stick with my own MapView
for now. I’m sure Apple will address this somehow. This was the only component I was disappointed with.
Grids
One thing lacking in SwiftUI 1.0 was a way to present collections in columns or rows. What people have done is wrap UICollectionView
to do this. With the new LazyVGrid
and LazyHGrid
you do not need to do that. The “lazy” part of their names means they won’t create any children until they need to. Some folks call these virtualized lists.
Laying out a grid is easy: You create an array of GridItem
instances that describe how many columns or rows there should be and how they should be sized (basically fixed or variable). Then you just fill them. If you need to scroll them, wrap them in a ScrollView
. Each element that’s being presented should be given enough information to fill itself out. I used a LazyHGrid
to present a strip of photos that were taken near a Journal entry’s location. As each thumbnail is exposed it requests a UIImage
from the Photo library on the device, showing an activity spinner (ProgressView
for SwiftUI 2.0) until the image is available.
Scrolling
Speaking of scrolling, one pretty irritating shortcoming in SwiftUI 1.0 was the inability to scroll to a specific element within a ScrollView
. To solve that, SwiftUI 2.0 introduces ScrollViewReader
which you place inside of a ScrollView
. If you want to have a specific item brought into view, just give the ScrollViewReader
its ID
. If you place your request inside of a withAnimation
block, the ScrollView
will slide nicely right to the item.
I used this in my Journal app. When the app opens it shows a calendar. If you tap on a date, the next screen is a list of all of the entries in the month and it scrolls the view to the date you selected. In the SwiftUI 1.0 version I used a wrapped UITableView
to accomplish that. Now I can discard that part to do this exclusively in SwiftUI 2.0. This really reduced the footprint of the app and it seems to be a bit quicker, too.
Tabs
You’re probably familiar with using a tabbed application: the bottom of the screen has icons and labels for different sections of the app. Facebook for example, has your home, market place, notifications, etc. Creating these with SwiftUI 2.0 and TabView
is pretty easy.
There is however, something more that TabView
can do for you: paging. You know those screens that have tiny dots at the bottom and you swipe left and right to go between them? Well by simply using the tabViewStyle
modifier with TabView
and giving it a PageTabViewStyle
specifier, you turn your old tabs into pages. Pretty handy.
I used this with my Journal app to present a set of full-size images that might be associated with a journal entry. Once you tap on a thumbnail in the LazyHGrid
, that action displays a new screen with the full size images. You can zoom and pan on them (like Instagram) or swipe to go to another image without returning to the previous screen to select a new image.
Final Thoughts
What impresses me most is that Apple really stuck with its view composition model. For example, the LazyHGrid
does not scroll by itself. If you want that behavior, place it inside of a ScrollView
. Likewise, you can add functionality with modifiers rather than having massive components with tons of options that you won’t use most of the time. Same goes for animation. The ScrollView
will not slide to show you the item you want, you have to trigger it by placing your change inside of an animation block. This allows you to decide if you want the list to bounce into view, zip and slow down, take 2 minutes, etc.; it’s up to you.
I gave you a little taste of what is available in SwiftUI 2.0. I did not include changes to the Combine framework and Swift 5.3, but those were also welcome and I barely touched the surface of what’s capable there.
If you have been holding off going to SwiftUI, now is the best time to jump in. It is far more robust and capable than SwiftUI 1.0 - and I built a commercial app with that!
It’s so much easier and faster to build apps with SwiftUI. All of the tedium of layout constraints, actions, outlets, storyboards, XIB files, are - poof - gone.
And you do not have to go 100% into a re-write. Using the SwiftUI UIHostingController
you can wrap SwiftUI components into your UIKit code. So if you have a new screen to develop that has animation or a tricky list layout, give SwiftUI 2.0 a try.
I will try and and do another one of these in a few months as I get more experience and we can compare notes.
Simple Charts with SwiftUI
Simple, animated, charts using SwiftUI
The information in this article is now obsolete with the introduction of iOS 16 and SwiftUI 4.0
This project shows one way to use SwiftUI to make some simple charts. There is a ColumnChart
, a BarChart
, a LineChart
, and a PieChart
that animate when their data changes. The data for these charts is an array of Double
values that represent percentages. To make things a little easier there is a ChartModel
that can take any array of Double
and transform it into percentage values.
An example shows how to use the chart views, including custom data formatters to show values on the charts. Axes and grid lines can also be applied.
You can find the source code for this on my GitHub repo.
The Charts
The charts themselves are pretty simple. They are composed of SwiftUI Shape
elements and then layered in a ZStack
to give the desired effect:
GridView
(optional)The chart (
BarChart
,ColumnChart, LineChart
, orPieChart
)AxisView
(optional)
Model
The data for the chart is an array of Double
values. The values are percentages of the chart's primary dimension (height for ColumnChart
, width for BarChart
). The model, ChartModel
, converts the user data (temperatures in my example) into percentages so the chart user does not have do that conversion. The model can either use the data or it can be given a specific range (eg, -100°F to +100°F) when calculating the percentages.
The model for the PieChart
is a little different so there is a PieChartModel
, a subclass of ChartModel
. The data for the PieChart
is represented by a pie wedge which is a percentage of the total. However, to make things easier for the PieChart
, the PieChartModel
transforms the data not into a vector of percentages, but a vector of angles (in degrees).
The percentages (or angles) are then stored in a very special data type: AnimatableVector
(see below) which is @Published
in the model. When the model's data changes it triggers a new set of values and refreshes this vector.
AnimatableVector
The key to SwiftUI animation is SwiftUI's ability to interpolate between starting and ending values. For example, if you have an offset of 10 and then change it 100, if the element has an .animation
modifier, SwiftUI will smoothly move the element from the first position to the next position.
SwiftUI makes it easy to animate its own modifiers like offset, scale, and opacity (among others). But you can create your own custom animatable data. Check out Paul Hudson's post on Animating Simple Shapes.
Unfortunately, SwiftUI does not provide more than animating a single value and a pair (AnimatablePair
). What's needed for a chart is animating an array, or vector, of values.
This is where Swift's VectorArithmetic
comes in. I won't go into details here, but thanks to Majiid Jabrayilov, there is AnimatableVector
which makes chart animation possible (see Acknowledgements below). By giving a set of values to the vector, a change in that set can be animated by SwiftUI, causing the chart to fluctuate. Its a nice effect and, of course, you can play with the effects used (I just use a simple ease-in-out, but you can use others).
The App
The code in this repository is a runnable app using SwiftUI 1.0. The app itself presents both types of charts with some toggles to turn on/off some of the features.
Please feel free to use this code for your projects or to use it as another way to understand animation or just SwiftUI in general. I will probably be updating it from time to time as I learn more and improve my techniques.
Acknowledgements
Without AnimatableVector this project would not be possible. My thanks to Majiid Jabrayilov for showing me how wonderful Swift math can be. Check out his article on the magic of animatable values.
My Swift life would not be complete without Paul Hudson's Hacking with Swift blog. The man must never sleep.
Also: if you are looking for a radar (or spider) chart, check out this article from Jimmy M Andersson: Data Visualization with SwiftUI: Radar Charts.
Using Size Traits with SwiftUI
Size traits make it easy for your app to handle different screen sizes and orientations.
So Many Sizes
This article was written for SwiftUI 1.0.
Nowadays mobile devices come in a dizzying array of sizes and shapes. I’ve got an Android tablet that’s much taller than it is wide (or much wider than it is tall). Apple products also have different sizes ranging from the Apple Watch, to the iPhoneSE, to the iPhone Pro Max, to the iPad, to the Apple TV, and to the iMac and MacBook computers. With Apple’s new generation of operating systems, you will be able to write apps using SwiftUI to run on all these devices with virtually the same code base. So how do you cope with all of the different screen sizes and resolutions?
If you are an Android developer you create layout files (among other things) for the different size screens your app supports. When your app runs, the OS finds the closest layout to the size of the screen of the device and loads that. Your app’s code just refers to the “login” layout and it’s none the wiser about the size of the screen. That’s all well and good, but if your app has a tricky layout, you might have to support a lot of layout files.
Apple has taken a different approach. While you can get the actual size of the device your app is running on and use those dimensions to programmatically set your UI, that’s probably the least attractive way to go. The better alternative is to use size traits.
Size Trait Classes
Size traits basically tell you the shape of the screen using two values in both dimensions. The two values are Regular
and Compact
. If you take an iPhone and hold it in portrait orientation, the size traits for this will be vertical Regular
(or just R
) and horizontal Regular
as well. In other words, this is the device’s regular orientation. If you turn it to landscape orientation, then the traits change to vertical Compact
(or just C
) and horizontal R
. The screen’s width is still considered normal or regular
but vertically it has been compacted.
If you use an iPad, your app’s size traits are always Regular
for both dimensions unless you use split screen (to share your app with another app). Then your horizontal size trait becomes Compact
. The same will be true with Max size iPhone in landscape mode. If you app supports the main-detail split screen view, your main screen will become Compact
.
If you are using UIKit and Storyboards, Interface Builder lets you set your constraints based on different size collections. That is not covered in this post.
The point is, your layout can just react to the size trait and not worry about the actual size or even orientation of the screen.
I’ll use one of my apps as an example. Below is the app running on an iPhone SE. On the left is the app in portrait orientation and the right is landscape orientation. Not very pretty in landscape - some parts have been cut off.
To solve that, I looked at the vertical size trait and adjusted the scaleEffect
modifier of the view to shrink the UI a little so it all appears. Now compare the two screens again:
Using Size Trait Classes
So how do you actually use size traits in a SwiftUI app? It’s actually really simple.
First, in the View
you want to adjust based on size, declare @Environment
vars for the orientations you are interested in handling. Here I am just looking at vertical:
@Environment(\.verticalSizeClass) var verticalSizeClass
Now I want to scale down my interface based on the size trait, so I set the .scaleEffect
modifier accordingly:
VStack(alignment: .center) {
// content
}.scaleEffect( (self.verticalSizeClass ?? .compact) == .compact ? 0.75 : 1.0 )
Note that
verticalSizeClass
is anOptional
so you need to unwrap it in some way.
When you change the orientation of the device, the verticalSizeClass
environment value will change and because SwiftUI is now observing it (because you are using it) the body
of your View
will automatically be re-run and the scale will change.
I went through each of my views in different orientations and on different devices, making note of any adjustments needed. For the View
above, scaling it seemed like the most logical choice to fit it all into the space and still be usable. But you have other choices:
Change the spacing between controls. I did that in another
View
and just reduced some of the white space.Change the size of fonts. You would be surprised what a slightly smaller font can do to make things fit nicely.
Re-arrange the screen as shown below.
On the left the two DatePicker
s are arranged vertically while in landscape, on the left, they are arranged horizontally.
if self.verticalSizeClass == .compact {
// arrange elements in an HStack
} else {
// arrange elements in a VStack
}
Using the size trait classes in SwiftUI is easy:
Declare the
@Environment
variable for the one(s) you want.Use a test in the layout code to determine how the UI should look based on the value,
Compact
orRegular
.
That’s all there is to it.
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.
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.
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
.
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
.
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.
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.
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):
In the schema editor, change the Application Language
and Application Region
drop-downs. Here I’ve changed them to “Dutch” and “Netherlands”.
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!
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!
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:
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.
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.
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 ofstate.amount
. Whenever you changeamount
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?
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 } } }
Declare the SectionModel to implement the Observable protocol. This will allow it to be a @ObservedObject later.
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.
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.
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) } } }
Create the CategoryHeader as a SwiftUI View struct. We are calling this “CategoryHeader” to avoid confusion with header part of the SwiftUI Section.
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).
The Image displayed depends on the state of the section’s “openness”.
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.
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() } } }
The outer ForEach is looping through your data model, extracting a single item.
The SwiftUI section is given the CategoryHeader defined earlier with the title and model (which will be defined in a moment).
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.