SwiftUI has a Picker view available with multiple different styles. One example of when it falls short is when we want to use a multi component picker with wheel style. One way how to try to achieve this is using a HStack with two Picker views, but it does not work very well, especially when trying to show it inside a Form view. So what else we can do? If something can’t be done in SwiftUI then we can use UIKit instead.
In my case, I wanted to create a picker which allows picking a date duration. It would have one wheel for selecting a number and the other wheel for selecting either days, weeks or months.
Firstly, let’s create a tiny struct which is going to hold the state of this picker. It needs to store a numeric value and the unit: days, weeks, months. Let’s name it as DateDuration. Since we want to iterate over the DateDuration.Unit, we’ll conform it to CaseIterable protocol.
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
UIPickerView in UIKit can do everything we want, therefore we’ll need to wrap it into a SwiftUI view. This can be done by creating a new type which conforms to UIViewRepresentable protocol. Also, we need a binding which holds the value of the current selection: when the user changes it, the binding communicates the changes back and vice-versa. Additionally, we’ll add properties for configuring values and units. UIPickerView us created and configured in the makeUIView(context:) function. UIPickerView is driven by a data source and a delegate, which means we require a coordinator object as well. Coordinator is part of the UIViewRepresentable protocol.
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
Coordinator is created in the makeCoordinator() function. It is going to do most of the work by providing data to the UIPickerView and handling the current selection. Therefore, we’ll store the selection binding, values, and units in the Coordinator class as well.
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
The last missing piece is implementing UIPickerViewDataSource and UIPickerViewDelegate methods in the Coordinator class. This is pretty straight-forward to do. We’ll need to display two components where the first component is the list of values and the second component is the unit: days, weeks, months. When the user selects a new value, we’ll change the DateDuration value of the binding.
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 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
SwiftUI provides wrappers for UIViewController and UIView on iOS. Same wrappers are also available for AppKit views on macOS. Let’s see how to use those wrappers for rendering UIKit views in SwiftUI previews and therefore benefiting from seeing changes immediately. Note that even when a project can’t support SwiftUI views because of the minimum deployment target, then this is still something what can be used when compiling the project with debug settings. Preview related code should only be compiled in debug builds and is never meant to be compiled in release builds. Before we jump in, there are two very useful shortcuts for keeping in mind: option+command+return for toggling previews and option+command+p for refreshing previews.
UIViewControllerRepresentable for wrapping UIViewControllers
UIViewControllerRepresentable is a protocol which can be used for wrapping UIViewController and representing it in SwiftUI. We can add a struct which conforms to that protocol and then creating an instance of the view controller in the makeUIViewController method. Second step is to add another struct which implements PreviewProvider protocol and which is used by Xcode for rendering previews. In simple cases we can get away only with such implementation but in more complex view controllers we would need to set up dependencies and generate example data for the preview. If need to do this, then all that code can be added to the makeUIViewController method.
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
Wrapping UIViewController with UIViewControllerRepresentable.
UIViewController shown using SwiftUI
UIViewRepresentable for wrapping UIViews
UIViewRepresentable follows the same flow. In the example below, we use Group for showing two views with fixed size and different appearances at the same 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
Wrapping UIView subclass with UIViewRepresentable.
Multiple UIViews shown in SwiftUI preview at the same time.
Summary
We looked into how to wrap view controllers and views for SwiftUI previews. Previews only required a little bit of code and therefore it is something what we can use for improving our workflows when working with UIKit views.