I have a tiny Swift package, what I have been using for reading and writing data on disk. Data is written to a subfolder in the documents folder. Beginning with iOS 16 there is a new way how to create that URL. It is such a tiny addition to what should have been there a long time ago.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here we can see that instead of a throwing function which has 4 parameters, we can replace it with a non-throwing type property. Finally! Not sure why it gives me so much happiness, maybe because I always forget the URL API whenever I need it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
KeyPathComparator was added to Foundation in iOS 15 and macOS 12. The KeyPathComparator is used by defining a key path which is used for fetching a value for comparison. Values are then compared with a SortComparator. In the simplest form we do not need to create the SortComparator ourselves and instead, ComparableComparator is created automatically. But if the value is String then String.StandardComparator.localizedStandard is used instead of ComparableComparator. All in all it is pretty much the similar to NSSortDescriptor which was used for sorting NSArray and NSMutableArray. New comparator types on the other hand can be used with many more types.
Using KeyPathComparator
As an example, let’s take a case of having an array of Player types where each player has played two rounds and therefore have two different scores. Additionally, each player type stores a competitor number as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we want to sort the array of players by first and second round scores then it goes like this (note that order is set to reverse which gives us descending order):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here we can see that sequences have sorted(using:) functions which take in either one comparator or several. An example of using several comparators is sorting the same players array by the highest score first and if two or more players hace the same highest score, then sorting by the worst score from these two rounds.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
WWDC’21 brought new APIs for creating attributed strings. AttributedString is a new value type which provides a type-safe API for dealing with attributes, its also localizable, supports limited amount of Markdown syntax, and can be even archived and unarchived thanks to the Codable support. In this blog post, we’ll take a look at the new API and will compose some attributed strings.
An AttributedString with attributes applied to multiple ranges.
The attributed string visible above contains multiple attributes starting with background color attribute and finishing with a link attribute. In the snippet below we can find different ways how to set attributes: searching for a range, manually creating a range, using AttributedContainer for setting multiple attributes at once, and also setting attributes to the whole string. As this string is displayed in a SwiftUI view then all the used attributes are part of the SwiftUI attribute scope.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we would like to see the ranges attributes were set to, we can use the runs API. A single run is a set of attributes shared by a single range. If we print all the runs, in this case 9, then it would look like this:
The {
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
SwiftUI.BackgroundColor = indigo
}
quick {
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
}
brown {
NSPresentationIntent = [paragraph (id 1)]
NSLanguage = en
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
SwiftUI.ForegroundColor = brown
}
fox {
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
}
jumps {
SwiftUI.UnderlineColor = cyan
NSLanguage = en
SwiftUI.Kern = 5.0
SwiftUI.BaselineOffset = 4.0
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSPresentationIntent = [paragraph (id 1)]
NSUnderline = NSUnderlineStyle(rawValue: 256)
}
over the {
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
}
lazy {
SwiftUI.StrikethroughColor = yellow
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
NSStrikethrough = NSUnderlineStyle(rawValue: 1)
}
{
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
}
dog {
NSLink = https://www.apple.com
SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5bb7d400).FontBox<SwiftUI.Font.(unknown context at $7fff5bbfdb68).SystemProvider>)
NSLanguage = en
NSPresentationIntent = [paragraph (id 1)]
}
The new AttributedString API also supports custom attributes. Custom attributes need to conform to a AttributeStringKey protocol in bare minimum. But when we would like to benefit from using the custom attribute in Markdown and also allowing to decode and encode it to data with Codable then we would need to conform to MarkdownDecodableAttributedStringKey and CodableAttributedStringKey respectively. In a simple example, let’s create a new attribute named MessageAttribute which can store a value of Message struct with id and value fields. The MessageAttribute needs to define the type it stores and a name used when encoding and decoding. In addition, we’ll need to add a new attribute scope which contains the new attribute. As we intend to use the new attribute in a SwiftUI app then we’ll add swiftUI attributes to the scope as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
With this set we can create attributed strings with this attribute either using markdown syntax or adding the attribute manually. Markdown syntax for custom attributes uses caret followed with square brackets and with the content in brackets after that. We also need to make sure to pass custom attribute scope into the AttributedString initializer as well. One thing what I have not figured out is how to create a completely custom appearance for custom attributes in SwiftUI views, like we can do in UIKit views.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We took a look on AttributedString, AttributeContainer, AttributeScope, and created attributed strings with the new API. With this knowledge, we got going with the new API and can continue exploring it further. The last thing to mention is that AttributedString can be converted to and from NSAttributedString with breeze.
I was looking at formatters provided by Foundation framework and this time I would like to put some attention on MeasurementFormatter. Like the name says, it is used for creating localized strings of some sort of measurements. Measurements are represented by a generic struct Measurement where the generic UnitType describes the unit represented by it. Apple provides an Unit subclass Dimension which in turn has a many subclasses on its own. At the time of writing there are 22 dimensional units available with each of those having multitude of related units. For example, UnitDuration provides units for seconds, minutes, and hours. The full list of Apple provided dimensions are available in a table here: Dimension.
Using MeasurementFormatter
MeasurementFormatter is simple to use. If we change the unitStyle property then we can configure how the unit is spelled in the localized string.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MeasurementFormatter also has an unitOptions property which controls the way how the final string is composed when taking account the current locale. For example, if locale is set to en_US then UnitTemperature measurement is formatted in Fahrenheits. If locale is set to en_GB then the measurement returns Celsius.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MeasurementFormatter selecting output unit based on the locale.
In case we would like to make sure the same unit is used, then we can use the providedUnit option.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Another thing to note is that the Measurement type also supports comparing measurements and mathematical operations. For example, we can add two measurements.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MeasurementFormatter is built in a way that it can support custom units as well. We could create a pseudo unit for a children’s game named UnitBanana.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MeasurementFormatter and Measurement types create a powerful combination which can be used for creating localized strings of values with an unit. Next time when you need to present a value with an unit, then check out the MeasurementFormatter first.
UILabel supports displaying attributed strings with link attributes but what it does not support is allowing to tap on hyperlinks and opening those in Safari. An Alternative way is using an UITextView which does support opening hyperlinks but on the other hand it is a more heavy view component and therefore might not be the best choice when we just need to display some text with hyperlinks. This time lets create a simple UILabel subclass which adds support for opening hyperlinks and custom hyperlink styles.
Creating NSAttributedStrings with hyperlinks
Before we jump into implementing an UILabel subclass HyperlinkLabel, let’s first take a look on how to create a NSAttributedString with hyperlinks. In the example app we will have several labels with different configurations: default and custom link styles and left, centre, right text alignments. UITextView has a linkTextAttributes property but in our UILabel subclass we’ll need to implement custom link styling ourselves. The approach we are going to take is creating a new NSAttributedString.Key value named hyperlink and adding text attributes when the attribute is present.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Let’s now create a convenience method which creates NSAttributedString and sets it to the HyperlinkLabel.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The final NSAttributedString is created by first initializing a mutable version with a format string. The format string follows the familiar format specifiers like we used with NSString and String APIs when dealing with re-orderable arguments. Format specifiers are replaced with new instances of NSAttributedStrings where the string value equals to a hyperlink name and the URL value is stored on the NSAttributedString.Key.link or NSAttributedString.Key.hyperlink attribute. The former gives us the default link style defined by Apple and the latter our custom link style.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Creating HyperlinkLabel which supports tapping on hyperlinks
UILabel does not provide access to its text layout and therefore it is not possible to know which hyperlink was tapped. For finding the tapped hyperlink we’ll need to use our own NSLayoutManager, NSTextStorage, and NSTextContainer. If we configure those properly we can figure out which character was tapped and therefore if it was part of the hyperlink. If it is, then we can let UIApplication to open the tapped URL. Let’s contain this in a function which gives us a configured NSTextStorage.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now we can use this function when handling UITouch events. We’ll add another private function which will take current UITouches and figure out which hyperlink was tapped. The general flow consists of creating a NSTextStorage with the function we just defined, then asking from the NSLayoutManager which character index was tapped. NSLayoutManager returns the closest character index of the touch. Therefore, we’ll need to go one step deeper and ask for the actual bounding rectangle of the glyphs representing the character and then verifying if the touch location was inside the glyph’s bounding rectangle. This is important when dealing with different text alignments and when tapping on the free space around characters. After figuring out which character was tapped we’ll need to check for the hyperlink attributes. If the tapped character has either attribute set, then we can conclude that we tapped on a hyperlink. This function can then be called in touchesEnded and if we tapped on a hyperlink, then we can open it. One thing to note is that userInteractionEnabled needs to be set to true before UILabel can handle touch events.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next thing we need to do is overriding attributedText property and handling the custom style of our custom hyperlink attribute. If the attributed text has this attribute set, then we will apply custom hyperlink text attributes. The same way as Apple’s link attribute works, when the attributed string gets displayed then custom styling is used. Secondly, we’ll set the UILabel’s font to the attributed string’s ranges which do not have a font attribute set. UILabel internally use UILabel’s font when font attributes are not set, so we want to force the same behaviour when the stored attributed string is set to our own NSTextStorage. If we do not do this, then NSAttributedString just uses its default font and the displayed string is not going to be equal to the string set to NSTextStorage. This in turn will lead to invalid character index calculations because fonts are different.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We created a UILabel subclass which has a capability of opening tapped links. Useful, if we need just a label for displaying some text along with hyperlinks. Please take a look on the full implementation available here: HyperlinkLabel.
In model-view-view model (MVVM) architecture the view model observes the model and provides data for the view by transforming it when needed. When the user interacts with the view and changes the data in it then the view model’s responsibility is to propagate those mutations back to the model object. Therefore, the important part in MVVM is how to manage data flows between objects. This time we’ll take a look on observing key-value observing (KVO) compatible model objects with Combine framework in SwiftUI view. The example view what we’ll build looks like this:
SwiftUI view which enables editing package related information and displays a summary of the package.
Model layer
The model object represents a package which contains information about the recipient, the sender, and the contents. The recipient and the sender are represented by a Person object which includes a first name, a last name, and a postal address. The contents is an array of immutable PackageContent objects. In Swift, we can use KVO by specifying @objc and dynamic modifiers on properties. Dynamic means that method dispatch is using objective-c runtime and therefore all the types must be representable in objective-c runtime. This immediately adds restrictions to the types we can use. When writing pure Swift code I do not recommend using KVO but sometimes we just need to use it. One example is NSManagedObject from the CoreData framework. But in this app we are not dealing with NSManagedObject but with a simple NSObject subclass instead.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The view object is responsible for describing the UI and rendering data represented by the view model. We have a simple form for modifying the recipient’s first name, last name, and the street name (for keeping this UI simple I left out all the other postal address related properties). At the bottom of the view we have a text object which just describes the package and a button for adding new items to the package’s contents. Whenever any of the package’s properties change, the view needs to reload. View reload is done through the @StateObject property wrapper (read mode about observing view models in MVVM in SwiftUI and @StateObject and MVVM in SwiftUI).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The view model’s responsibility is to observe the model object and propagating view changes to the model. It acts as a transformation layer where we can transform any data in the model to anything suitable for displaying. In the example below we are reading CNPostalAddress and only returning street name and reading multiple properties and returning a summary string. View models make it easy to contain such logic and also make it more easy to test.
Foundation framework defines a publisher named NSObject.KeyValueObservingPublisher which can be used for observing KVO compatible properties. One of the approaches is to use this publisher and then bind the model changes to the view model’s own property. Combine framework provides a convenient assign operator which takes a target publisher as an argument. Convenient because we can connect it with @Published properties in the view model. @Published properties automatically notify the ObservableObject’s objectWillChange publisher which is observed by a SwiftUI view. As soon as the property changes, SwiftUI view picks up the change and reloads itself. Note that we’ll also need to propagate changes back to the model when user updates the view and therfore the @Published property. This can be achieved by connecting property’s publisher with dropFirst, removeDuplicates and assign publishers where the latter assigns the value to the model object. Drop first is used for ignoring the initial value of the property. One downside is that now we can have the same information both in the view model and in the model. But on the other hand it makes the data streams easy to read and no need to have extra observation for triggering the view reload by manually calling send() on the objectWillChange publisher.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Another approach what we can use is providing properties in the view model which return a Binding. This allows us to write the transformation code inside the get and set closures. This is what we have done with the street property. Note that we’ll still need to observe the model as the model can change at any point. Binding just provides a way of accessing the value. Therefore, we’ll need to set up an observation and calling send() on the objectWillChange publisher.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we go back to the SwiftUI view and connect all the properties then the full implementation of the view model looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Key-value observing is getting less and less used after the introduction of Combine and SwiftUI. But there are still times when we need to connect good old KVO compatible NSObject subclasses with a SwiftUI view. Therefore, it is good to know how to handle KVO in SwiftUI views as well.
Git hooks are scripts written in any scripting language and are triggered when important actions occur. Hooks are stored in the repository’s .git/hooks folder. The script needs to have an appropriate filename without a path extension and also have executable permissions. Push event is an excellent time for triggering unit-tests and making sure local changes have not broken any. Therefore we’ll look into how to create a pre-push script for an iOS project in Swift.
Quick introduction to scripts written in Swift
Setting up a simple script in Swift follows steps familiar from other languages. The script file needs to start with #!/usr/bin/swift followed by the actual script. A simple example script can look like this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The command for running the script is swift Script.swift (if the filename is Script.swift). Another way is making the script executable by adding executable permissions to the file by running chmod +x Script.swift command. Then the script can be run with ./Script.swift (makes sense to drop the file extension).
Building a Xcode project for testing
The pre-push script contains of 3 steps: building the project for testing, running tests, and finally printing out the code coverage results. The first step catches build errors, the second step test failures, and the third step prints out code coverage results. Code coverage can be enabled in the scheme’s test action or adding -enableCodeCoverage YES to the xcodebuild command. Before we jump into creating a xcodebuild command with correct arguments then we’ll need to tackle the problem of calling the xcodebuild command line application from the Swift script. Command line applications can be invoked with the Foundation’s Process class. We’ll add an extension which deals with launching a specified command with zsh and printing out the standard output and error.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The next step in the script is to define the project related configuration and create the xcodebuild command. All the user defined arguments are wrapped in quotes for avoiding any issues with whitespaces. The command is pretty straight-forward. If there are any build errors then the result code is not equal to 0. Then we can use the same error code for exiting the Swift script with exit() function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The command used for running unit-tests is fairly similar. Instead of build-without-testing we are using test-without-building argument and additionally provide a path where the result bundle is written to. This bundle contains information about the test run. Note that this path must not exist, otherwise xcodebuild stops with an error. Therefore we delete the existing file before running the command. Moreover, when there is a failure, we’ll clean up the path as well – pre-push script should not leave any temporary files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Last step is optional but it is nice to see code coverage information when pushing changes to a server. Xcode provides a command line application for viewing coverage data in human readable form. One of the options is printing out code coverage per target which gives a nice and concise overview.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We looked into how to create a pre-push script in Swift. It called other command line applications for building the project, running the tests, and printing out code coverage information. The full script is available below, feel free to copy-paste it to your projects. The one last thing to consider is adding an alias in Terminal for easy installation: alias xcode_pre_push_add='cp ~/Dev/pre-push .git/hooks/pre-push && mate .git/hooks/pre-push' This just copies it from predefined location to the repository checkout and opens it in an editor for setting project related settings (replace mate with any editor).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
iOS apps can add quick actions which are displayed when force touching on the app’s icon. We’ll add quick actions to my open source WaterMyPlants example app. Quick actions can be static and dynamic: static actions are defined in the Info.plist and dynamic actions are configured in the code by updating UIApplication’s shortcutItems property. In the WaterMyPlants app, we’ll add one static action for adding a new plant and dynamic actions for opening added plants.
Home screen quick actions.
Static quick actions
Static quick actions are defined in the Info.plist. We need to add the UIApplicationShortcutItems key with array of dictionaries. Every dictionary in the array defines a quick action. Quick actions are required to have type and title and optionally we can add a subtitle and an icon. In the example below, we used one of the predefined icons. Predefined icons can be used by adding a key UIApplicationShortcutItemIconType with a string matching a format UIApplicationShortcutIconType<name>. Custom images are defined by the UIApplicationShortcutItemIconFile key where the string value is the name of an image in the asset catalog.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Actions which depend on the data or state of the app can be added by setting the UIApplications’s shortcutItems property. Note that when adding items then we can use UIApplicationShortcutIcon‘s systemImageName initializer which enables us using any of the SF Symbols. Otherwise it is pretty much the same as defining a static quick action: setting type and title. It is useful to add an enum containing all the action types which becomes handy when we are handling actions. WaterMyPlants app uses scene delegates and therefore a good time to set dynamic quick actions is when the scene is resigning active status (see sceneWillResignActive(_:)).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Quick actions are handled either in the UIApplicationDelegate or in the UISceneDelegate. WaterMyPlants uses scene delegates, therefore we’ll look into how to perform actions using scene delegates. We need to keep in mind that when selecting a shortcut launches the app, then we would need to check the shortcut property in the UIScene.ConnectionOptions and use it for configuring the UI to perform the action (windowScene(_:performActionFor:completionHandler:) is not called in that case). But if the app is already running in the background, then we can handle the action in the performActionFor delegate callback.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We added home screen quick actions to the WaterMyPlants app. We looked into how to add static and dynamic quick actions and how to perform the selected action.
I was looking into creating a view which has TextField with NumberFormatter. Typed text would show up in a separate label and when trying to enter non-numbers, the TextField would reject those characters. Although TextField component in SwiftUI has generic initialiser init(_:value:formatter:onEditingChanged:onCommit:) it does not seem to do what we need. Value binding does not update while typing, non-number characters are not discarded, and string is not reloaded when view reloads with different model data. Therefore, I decided to create a wrapper around TextField which deals with transforming numbers to strings and implements all the before mentioned features.
End result after creating a custom NumberTextField.
Content view with temperature limits
Example use-case is basic view for editing temperature limits where model type will force high value to be at least 10 units higher compared to low value. The model type also have separate properties for getting NSNumber instances what we use later (such conversion could also happen on SwiftUI level).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Model type storing temperature limits which are forced to have 10 unit difference.
Content view has text fields, button for randomising limits and label for displaying current values. NumberTextField is a custom view which implements all the features what we listed in the beginning of the post.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
View with custom NumberTextFields bound to temperature limits.
Creating NumberTextField with NSNumber binding and NumberFormatter
NumberTextField is a wrapper around TextField and internally handles NSNumber to String and String to NSNumber transformations. Transformations happen inside a separate class called StringTransformer which stores editable string in @Published property. @Published property is first populated with string value by transforming NSNumber to String using the formatter. Changes made by user are captured by subscribing to stringValue publisher (@Published properties provide publishers). String to NSNumber transformation is tried when user edits the string: if successful, NSNumber is send back to model using the value binding, if fails, stringValue is set back to previous value. Note that dropFirst skips initial update when setting up sink and receive operator is used for scheduling updates at later time when SwiftUI has finished current layout update cycle.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
NumberTextField transforming NSNumber to String using NumberFormatter and vice versa.
Summary
TextField’s formatter initialiser does not seem to be operating as expected and therefore we built a custom view. It handles number to string transformations and refreshes the view when string can’t be transformed to number. Hopefully future SwiftUI iterations will fix the init(_:value:formatter:onEditingChanged:onCommit:) initialiser and NumberTextField is not needed at all.
Everything can’t go exactly as planned and therefore, at some point, there is a need for presenting localized error messages to the user. Let’s take a look at how to add custom error type what provides error description, failure reason and recovery suggestion and presenting it in SwiftUI view.
Adding custom error type
Custom error type is needed when we want to propagate errors using Swift’s error handling mechanism. Custom error types need to, at minimum, conform to Error protocol which defines localizedDescription property. If we would like to provide more information to users, including recovery suggestions, then we need to use LocalizedError instead. LocalizedError inherits from Error and defines additional properties which are intended for describing the error further. Note that LocalizedError is very similar to NSError: errorDescription, failureReason, recoverySuggestion, helpAnchor are all represented by NSError.UserInfoKey.
In the example app we’ll use LoginError currently definiing only one error: incorrectPassword.
enum LoginError: LocalizedError {
case incorrectPassword // invalidUserName etc
var errorDescription: String? {
switch self {
case .incorrectPassword:
return "Failed logging in account"
}
}
var failureReason: String? {
switch self {
case .incorrectPassword:
return "Entered password was incorrect"
}
}
var recoverySuggestion: String? {
switch self {
case .incorrectPassword:
return "Please try again with different password"
}
}
}
Presenting error in SwiftUI
Custom error defined, the next step is to present the error using SwiftUI’s alert view modifier: alert(isPresented:content:). Alert view modifier requires boolean binding and Alert container defining title, optional message, and buttons. In the example below, error is handled by the view model and Alert itself is created using convenience initializer which we’ll look at a bit later. Convenience initializer makes the view implementation more readable and reduces code duplication.
Alert view modifier requires a boolean binding controlling if the alert is visible or not. When alert is dismissed, SwiftUI automatically calls the binding with false, indicating that the alert should not be visible anymore. Note that force unwrap is safe here because view model makes sure isPresentingAlert never returns true when underlying error is nil.
final class ContentViewModel: ObservableObject {
@Published private(set) var activeError: LocalizedError?
var isPresentingAlert: Binding<Bool> {
return Binding<Bool>(get: {
return self.activeError != nil
}, set: { newValue in
guard !newValue else { return }
self.activeError = nil
})
}
func showAlertView() {
activeError = LoginError.incorrectPassword
}
}
Boolean binding is created manually and implemented in such way that when activeError is set, isPresentingAlert returns true. When alert is dismissed, set will clear the current active error. This approach makes it simple to handle any errors conforming to LocalizedError in the view model. Like mentioned before, LocalizedError enables us to add detailed information about the alert and we can use that when creating the Alert. Let’s take a look on it next.
Alert’s extension has initializers both for LocalizedError and NSError. NSError is used a lot in Objective-C frameworks so there is high probability that we need to present NSError in the future as well. Here, we can use Swift language’s built-in support of converting Swift error type to NSError and therefore we can implement convenience method only once for NSError. LocalizedError can be bridged to NSError and Swift compiler takes care of keeping the information about the error. In this implementation, I decided to include both the failureReason and recoverySuggestion when creating the message for Alert. This enables custom error types to choose how much information they provide (choosing which properties return text). Moreover, it is better to show as much information about the error as possible.
LoginError.incorrectPassword presented by Alert in SwiftUI
Summary
We created a custom error type and used LocalizedError instead of Error for making it suitable for displaying as an alert. We looked into how to use alert view modifier and MVVM together and introduced design pattern for easy alert presentation. If you need action sheet, then follow similar steps but use actionSheet view modifier with ActionSheet container.