Property wrappers allow property declaration to state what kind of property wrapper is used for implementing the property. We can use it for implementing transformations on properties like validating if string is email or not. This is what we will do: creating a property wrapper for email properties and validating emails using NSDataDetector. If value being set is email, we store it and if it is not, we set the property to nil instead.
Creating property wrapper
Property wrappers are types annotated with @propertyWrapper. The type needs to implement one property: wrappedValue. Emails are represented with strings, therefore our wrappedValue property is optional string. Optional is required, because string can contain invalid email and in that case we set the property to nil. Whenever we would like to use this property wrapper, we just need to add @EmailValidated in front of the property definition.
This file contains hidden or 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
Validating emails using regular expressions is not easy. Fortunately Apple provides API exactly for this: NSDataDetector. We can create an instance of NSDataDetector with specifying link as detected types. When matching emails, we use anchored option as we expect the string to only include email, nothing else. Anchored will tell the data detector to match starting with the first character. As firstMatch(in:options:range:) uses NSRange, we need to convert Swift’s range to NSRange because those ranges do not have one-to-one match. For this, we can use special NSRange initialiser taking Swift string and its range.
NSDataDetector represents links with URL, therefore we will see if match contains an URL and if URL’s scheme is mailto. If it is, we can extract the matched email and return it.
This file contains hidden or 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
For using the created property wrapper, all we need to do is to annotate property with @EmailValidated.
This file contains hidden or 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 simple property wrapper for validating emails. We saw that creating a property wrapper for validating email with NSDataDetector requires only a little bit of code.
When writing code in Swift it is often needed to observe changes in other objects. We can use Apple’s key-value observation but it has some implications: requires to use NSObject and dynamic dispatch through Objective-C runtime. This time, let’s build a simple key-value observation in Swift what does not require to use NSObject at all. Although it is far from being as feature complete as Apple’s implementation, it delivers the basic use-case which is often all what we need.
Custom KeyValueObservable protocol
The approach we take here is defining a protocol, providing default implementations for all the functions. Then we can make any class to conform to this protocol, but as we need to store observation related information, then the class needs to define a property holding an instance of ObservationStore. Secondly, it is required to send key-value change notification manually using didChangeValue(for:).
Add observer function returns an instance of Observation what can be used for removing the added observation. If the observer does not need to be removed during the lifetime of the observer, it can be ignored. Observation is always cleaned up automatically next time any key value changes happen after observer is deallocated. This is due to the fact that observation handler captures observer weakly and during key-value changes, it is checked if the object is still alive or not.
This file contains hidden or 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
/// Adds observer for key path and returns observation token.
/// – Note: Observation token is only useful if it is needed to remove observation before observer is deallocated. When observer is deallocated, then observation is removed when next key value change is handled.
When adding observer, we create an observation handler what captures self and observer weakly. Handler returns boolean, what tells if the handler is still valid or not. Handler is not valid when observer has been deallocated since the last change. Otherwise handler is valid and should not be removed automatically.
This file contains hidden or 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
As mentioned before, ObservationStore is needed to added to every class conforming to KeyValueObservable protocol. It stores all the observations and restricts the access to modifying the observations directly from the observable class.
Observation is a simple struct containing an identifier and subtype defining the observation options. In this basic case, it just has initial option what assures handler is called immediately when adding an observer.
This file contains hidden or 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
In this small example a class Event conforms to KeyValueObservable and ViewController observers the title change and updates a label.
This file contains hidden or 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 time we added basic support for observing key paths without using key-value observing APIs known already from Objective-C times. The added KeyValueObservable protocol is easy to add to existing classes but requires manually calling didChangeValue(for:) for every property change.
Inspiration came from Observers in Swift part 2 (Swift by Sundell).
iOS 13 added a new class named UIContextMenuInteraction what is used for attaching menus to views. When adding menu interaction to a view and user uses 3D Touch or long press gesture on devices not supporting it, a menu is presented alongside with the highlighted content view. Therefore depending on the available space, not all the menu items can fit into the menu.
Setting up UIContextMenuInteraction
UIContextMenuInteraction is initialised with a delegate. Delegate’s job is to create an instance of UIContextMenuConfiguration with provider block for creating a menu when needed. It also should be noted that delegate can return nil in what case no menu is shown.
This file contains hidden or 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
Action provider of UIContextMenuConfiguration is a function taking in suggested menu items and returning an instance of UIMenu. Suggested menu items are the ones provided by responders from responder chain. For example we could use it for sharing actions among multiple responders.
Every menu item is represented by UIAction or another UIMenu allowing to have nested menus. UIActions have title and optionally image and state icon if the state is on. In addition we can explicitly disable actions and set a destructive appearance.
This file contains hidden or 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
Apple’s Vision framework contains computer vision related functionality and with iOS 13 it can detect text on images as well. Moreover, Apple added a new framework VisionKit what makes it easy to integrate document scanning functionality. For demonstrating the usage of it, let’s build a simple UI what can present the scanner and display scanned text.
Cropping text area when scanning documents
Scanning text with Vision
VisionKit has VNDocumentCameraViewController and when presented, it allows scanning documents and cropping scanned documents. It uses delegate for publishing scanned documents via an instance of VNDocumentCameraScan. This object contains all the taken images (documents). Next, we can use VNImageRequestHandler in Vision for detecting text on those images.
final class TextRecognizer {
let cameraScan: VNDocumentCameraScan
init(cameraScan: VNDocumentCameraScan) {
self.cameraScan = cameraScan
}
private let queue = DispatchQueue(label: "com.augmentedcode.scan", qos: .default, attributes: [], autoreleaseFrequency: .workItem)
func recognizeText(withCompletionHandler completionHandler: @escaping ([String]) -> Void) {
queue.async {
let images = (0..<self.cameraScan.pageCount).compactMap({ self.cameraScan.imageOfPage(at: $0).cgImage })
let imagesAndRequests = images.map({ (image: $0, request: VNRecognizeTextRequest()) })
let textPerPage = imagesAndRequests.map { image, request -> String in
let handler = VNImageRequestHandler(cgImage: image, options: [:])
do {
try handler.perform([request])
guard let observations = request.results as? [VNRecognizedTextObservation] else { return "" }
return observations.compactMap({ $0.topCandidates(1).first?.string }).joined(separator: "\n")
}
catch {
print(error)
return ""
}
}
DispatchQueue.main.async {
completionHandler(textPerPage)
}
}
}
}
Presenting document scanner with SwiftUI
As VNDocumentCameraViewController is UIKit view controller we can’t directly present it in SwiftUI. For making this work, we’ll need to use a separate value type conforming to UIViewControllerRepresentable protocol. UIViewControllerRepresentable is the glue between SwiftUI and UIKit and enables us to present UIKit views. This protocol requires us to define the class of the view controller and then implementing makeUIViewController(context:) and updateUIViewController(_:context:). In addition, we’ll also create coordinator what is going to be VNDocumentCameraViewController’s delegate. SwiftUI uses UIViewRepresentableContext for holding onto the coordinator and managing the view controller updates behind the scenes. Our case is pretty simple, we just use completion handler for passing back scanned text or nil when it was closed or error occurred. No need to update the view controller itself, only to pass data from it back to SwiftUI.
ContentView is the main SwiftUI view presenting the content for our very simple UI with static text, button and scanned text. When pressing on the button, we’ll set isShowingScannerSheet property to true. As it is @State property, then this change triggers SwiftUI update and sheet modifier will take care of presenting ScannerView with VNDocumentCameraViewController. When view controller finishes, completion handler is called and we will update the text property and set isShowingScannerSheet to false which triggers tearing down the modal during the next update.
struct ContentView: View {
private let buttonInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
var body: some View {
VStack(spacing: 32) {
Text("Vision Kit Example")
Button(action: openCamera) {
Text("Scan").foregroundColor(.white)
}.padding(buttonInsets)
.background(Color.blue)
.cornerRadius(3.0)
Text(text).lineLimit(nil)
}.sheet(isPresented: self.$isShowingScannerSheet) { self.makeScannerView() }
}
@State private var isShowingScannerSheet = false
@State private var text: String = ""
private func openCamera() {
isShowingScannerSheet = true
}
private func makeScannerView() -> ScannerView {
ScannerView(completion: { textPerPage in
if let text = textPerPage?.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines) {
self.text = text
}
self.isShowingScannerSheet = false
})
}
}
Summary
With the new addition of VisionKit and text recognition APIs, it is extremely easy to add support of scanning text using camera.
Let’s build a conversation view which shows a list of messages and has input text field with send button. Sent and received messages are managed by Conversation object. Conversation object manages a Session object which is simulating networking stack. This kind of setup allows us to look into how to propagate received messages from Session object to Conversation and then to the list view. We’ll jump into using types Combine and SwiftUI provide therefore if you need more information, definitely watch WWDC videos about Combine and SwiftUI.
Data layer
In the UI we are going to show a list of messages, therefore let’s define a struct for a Message. We’ll make the Message to conform to protocol defined in SwiftUI – Identifiable. We can add conformance by adding id property with type UUID what provides us unique identifier whenever we create a message. Identification is used by SwiftUI to identify messages and finding changes in the messages list.
This file contains hidden or 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
Session is owned by Conversation and simulates a networking stack dealing with sending and receiving messages. This like a place were we could use delegate pattern for forwarding received messages back to the Conversation. Instead of delegation pattern, we can use Combine’s PassthroughSubject. It enables us to publish new messages which we can then collect on the Conversation side. Great, but let’s see how to receive messages which are published by PassthroughSubject.
This file contains hidden or 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
Conversation is responsible of receiving messages from the Session and keeping the current history: list of messages. For receiving messages published by Session, we can use a subscriber called sink, which just gives access to values flowing through the channel. Subscribers are added directly to publishers, then publisher sends a subscription object back to the subscriber what subscriber can use for communicating with publisher. Here, communicating means requesting values from publisher. To recap: Session owns PassthroughSubject what Conversation starts to listen by attaching subscriber to it.
Conversation conforms to SwiftUI’s ObservableObject. When marking properties with @Published property wrapper, changes in those properties trigger updates in SwiftUI.
This file contains hidden or 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
In SwiftUI, views are described by value types conforming to View protocol. Every view return their content in the body property. Our UI is simple enough and requires to add navigation view, list and then input view. List is the table view construct which creates new rows whenever it needs to. As we made Message to conform to Identifiable, then we can pass the messages directly to the List.
This file contains hidden or 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
Input view contains text field and button for sending the entered message. Input text is local state owned by the view itself. @State is a property wrapper and internally it creates a separate storage where the input text is stored and read during view updates.
This file contains hidden or 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 have a the whole picture put together. Conversation object manages messages and lets SwiftUI know when it changes by using @Published property wrapper. When property wrapper dispatches change to SwiftUI, it compares the changes in the view hierarchy and updates only what is needed.
Summary
We created a basic list view what displays messages in the conversation object. We used simple constructs for passing on the data down from the Session to the SwiftUI layer. The aim of the sample project was to try out some of the ways Combine and SwiftUI allow us to build views.
So far we have been using CommonCrypto when it has come to creating hashes of data. I even wrote about it some time ago and presented a thin layer on top of it making it more convenient to use. In WWDC’19 Apple presented a new framework called CryptoKit. And of course, it contains functions for hashing data.
SHA512, SHA384, SHA256, SHA1 and MD5
CryptoKit contains separate types for SHA512, SHA384 and SHA256. In addition, there are MD5 and SHA1 but those are considered to be insecure and available only because of backwards compatibility reasons. With CryptoKit, hashing data becomes one line of code.
This file contains hidden or 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
In case we do not have the whole data available in memory (e.g. really huge file), new types support creating hash by feeding data in piece by piece (just highlighting here how to use the hasher with incremental data).
This file contains hidden or 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
Apple has an excellent playground describing the common operations developers need when using CryptoKit. Highly recommend to check it out if you need something more than just creating hashes.
Summary
CryptoKit is long waited framework what is easy to use and does not require managing raw pointers what was needed to when using CommonCrypto. It now just takes some time when we can bump deployment targets and forget CommonCrypto.
Testing networking code might sound tricky at first but in reality, it just means using custom URLProtocol what returns data we would like to. This allows testing the networking module without mocking URLSession. Using this approach we could do so much more, even integrating a third party networking library.
Networking class wrapping URLSession
Firstly, let’s set up a simple WebClient class what uses URLSession for initiating networking requests. It has a fetch method for loading URLRequest and transforming the response to expected payload type using Codable. As payload can be any type, we use generics here. Note that we need to pass in the payload type as a variable because we need the exact type when decoding the JSON data. How can we test this as URLSession would try to send an actual request to designated URL? As unit tests should behave exactly the same all the time and should not depend on external factors, then using a separate test server is not preferred. Instead, we can intercept the request and provide the response with custom URLProtocol.
This file contains hidden or 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
URLProtocol is meant to be overridden. Firstly, we’ll need to override canInit(with:) and return true here allowing URLSession to use this protocol for any URL request. Secondly, it is required to override canonicalRequest(for:) where we can just return the same request. Thirdly, startLoading, where we have the loading logic which uses class property for returning appropriate response. This allows us to set this property in unit tests and then returning the result when URLSession handles the fetch request. Finally, URLProtocol also needs to define stopLoading method what we can just leave empty as this protocol is not asynchronous.
This file contains hidden or 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
Using TestURLProtocol for mocking network requests in unit tests
Setting up a unit test requires to set the TestURLProtocol’s loadingHandler and returning the data we would like to. Then we create URLSessionConfiguration and set our TestURLProtocol to protocolClasses. After that we can use this configuration for initialising URLSession and using this session in our WebClient which handles fetch requests. That is pretty much all we need to do for testing networking requests.
This file contains hidden or 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
Testing networking code at first might sound daunting. But actually it just boils down to using custom URLProtocol and providing response we need to in our test.
Structs can’t be directly stored in UserDefaults because UserDefaults does not know how to serialize it. As UserDefaults is backed by plist files, struct needs to be converted representation supported by it. The core idea boils down to the question, how to convert struct into Dictionary.
Converting struct to Dictionary
The most straight-forward way would be to manually create dictionary by adding all the properties one by one. Depending on the struct, it might get pretty long and also requires maintenance when changing the struct. Therefore, let’s take a look on a way of converting struct into JSON representation instead. This can be achieved by conforming to Encodable and using JSONEncoder and JSONSerialization. In the same way, we can convert Dictionary back to struct with JSONSerialization, JSONDecoder and conforming to Decodable. When conforming struct to Encodable and Decodable, Swift compiler will take care of generating default implementations for methods in those protocols. JSONEncoder and JSONDecoder use those methods for converting struct to Data and back. It should be noted that we could just store data in user defaults. But as data is not human-readable, let’s convert it to Dictionary instead.
This file contains hidden or 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
Adding DictionaryConvertible and DictionaryDecodable
This implementation can be made a bit more usable by using protocols and protocol extensions for providing default implementations. This makes it extremely easy to adopt this to any objects.
This file contains hidden or 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
Using Swift’s Codable together with JSONEncoder, JSONDecoder and JSONSerialization we can skip writing code for converting data into different types and instead, providing a concise implementation for turning structs into dictionaries. We only talked about structs but this approach can be applied to classes as well.
UIViewPropertyAnimator enables configuring animations which can be modified when running. Animations can be paused and progress can be changed allowing to build interactive animations. UIViewPropertyAnimations are in stopped state by default. If we want to run the animation immediately, we can use class method runningPropertyAnimator(withDuration:delay:options:animations:completion:). UIViewPropertyAnimator gives us a lot of flexibility when it comes to composing animations and controlling them. Therefore let’s build an animation consisting of rotating and moving a view out of the visible rect.
Adding a view to animate
Firstly, we need to add a view which we are going to animate using UIViewPropertyAnimator. View is a UIView subclass which just overrides layerClass and returns CAGradientLayer instead. View is positioned into initial place using auto layout.
This file contains hidden or 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
UIViewPropertyAnimator has several initialisers allowing to control the used timing function. In this example we’ll just use built-in ease in and ease out timing. This just means animation pace increases in the beginning and slows down at the end of the animation. In addition to mentioned UICubicTimingParameters (ease in and ease out), there is support for UISpringTimingParameters as well. Both timing parameters can be passed in using the convenience initialiser init(duration:timingParameters:). The animation is configured to rotate the view by 90 degrees and move the view following a spline created by current point of the view and two other points. When animation ends, we reset the transform and tell auto layout to update the layout which will just move the view back to the initial position.
This file contains hidden or 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
Interrupting animation with UIPanGestureRecognizer
UIPanGestureRecognizer is used for interrupting the animation. When user starts dragging a finger on the screen, we capture the current animation progress and the initial point of the touch. Then, we can update the animation progress when dragging the finger to the left or right. When moving the finger back and forth, we can move the animation forward or backwards. As soon as letting the finger go, we start the animation which continues the animation from the update fractionComplete. The constant 300 is just a value defining the amount user needs to move the finger to be able to change the fractionComplete from 0.0 to 1.0.
This file contains hidden or 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 UIViewPropertyAnimator we can build interactive animations with a very little code. Its API allows controlling the flow of the animations by pausing the animation and controlling the progress of the animation dynamically.
UICollectionViewFlowLayout is layout object supplied by UIKit and enables showing items in grid. It allows customising spacings and supports fixed size cells and cells with different sizes. This time I am going to show how to build a collection view containing items with different sizes where sizes are defined by auto layout constraints.
Cell sizes in UICollectionViewFlowLayout
By default UICollectionViewFlowLayout uses itemSize property for defining the sizes of the cells. Another way is to use UICollectionViewDelegateFlowLayout and supplying item sizes by implementing collectionView(_:layout:sizeForItemAt:). Third way is to let auto layout for defining the size of the cell. This is what I am going to build this time.
This file contains hidden or 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
Setting up collection view cell with auto-layout constraints
When layout object is configured, second step is to create a cell. In the current prototype it is going to be a simple cell with a label and border around the label. Constraints are set up to have a 8 points space around the label. Constraints together with label’s intrinsicContentSize define the minimum size for the cell. If text is longer, intrinsicContentSize is wider.
This file contains hidden or 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
When putting all the things together, the end result is a collection view where every cell has its own size. Moreover, it supports dynamic type and cells will grow if user changes default text sizes.
Summary
UICollectionViewFlowLayout’s estimatedItemSize enables using auto-layout for defining cell sizes. Therefore creating cells where text defines the size of the cell is simple to do on iOS.