- Published on
Creating a Custom Font Picker in and for SwiftUI
Over the past year, SwiftUI has gotten more robust and mature than it was just a couple of years ago. However, there are still quite a few UIKit elements missing in SwiftUI — and one of them is the UIFontPickerViewController
. This UIKit class is a subclass of UIViewController
and is used to allow the users to pick a font that is preinstalled on their device. This allows a whole new dimension of customisability
and is something that I am working on with the new update of Sticker Cards .
In this tutorial, we’ll build a simple bridge that brings this powerful feature to SwiftUI. Excited? Let’s get started.
Configuring the ContentView
In our sample app’s ContentView
, which is the app’s root view, we’ll create a Text view and a Button view. The Text view will show a sample text upon which we’ll apply the custom font that the user will pick from the UIFontPickerViewController
. The Button will be used to present this font picker controller. ContentView
, should look like this:
struct ContentView: View {
var body: some View {
VStack(alignment: .center, spacing: 15) {
Text("Do what you can, with what you have, where you are.")
.fontWeight(.medium)
.multilineTextAlignment(.center)
Button(action:{}) {
Text("Present font picker")
}
}.padding()
}
}
This view does nothing other than show the two views we’ve discussed above. Before we add any functionality to ContentView
, let’s create the bridge that we’ll use to bring the UIFontPickerViewController
into SwiftUI.
CustomFontPicker
Create a new Swift file and call it CustomFontPicker. This file will contain our bridge. Start by importing the UIKit and SwiftUI frameworks. We’ll now create a new representable view controller struct called CustomFontPicker
. We’ll conform this struct to the UIViewControllerRepresentable
protocol. Your CustomFontPicker struct should now look like this:
struct CustomFontPicker: UIViewControllerRepresentable {
}
As soon as you add the protocol conformance, Xcode won’t be very pleased with you until you add the protocol stubs. So let’s do that now. Add these two methods into the CustomFontPicker struct:
func updateUIViewController(_ uiViewController: UIFontPickerViewController, context: UIViewControllerRepresentableContext<CustomFontPicker>) {
}
func updateUIViewController(_ uiViewController: UIFontPickerViewController, context: UIViewControllerRepresentableContext<CustomFontPicker>) {
}
Before going any further, let’s create the action handler that will be triggered when the user picks a new font from the list of fonts. We’ll also add an Environment property wrapper that will later be used to dismiss this view once the font has been successfully picked. Add these two properties to the CustomFontPicker
struct now:
@Environment(\.presentationMode) var presentationMode
private let onFontPick: (UIFontDescriptor) -> Void
onFontPick
is a simple action handler that will provide a UIFontDescriptor
callback once triggered. Let’s now initialise this using an escaping closure inside of our struct’s initialiser. Add this code to CustomFontPicker
:
init(onFontPick: @escaping (UIFontDescriptor) -> Void) {
self.onFontPick = onFontPick
}
It’s now time to make the Coordinator
class that — you guessed it — will coordinate the whole operation. This class will contain the delegate callbacks for the UIFontPickerViewController’s delegate. Inside of the CustomFontPicker
struct, create a nested class called Coordinator
. Conform Coordinator to NSObject
and UIFontPickerViewControllerDelegate
. The Coordinator class should now look like this:
class Coordinator: NSObject, UIFontPickerViewControllerDelegate {
}
Inside of this class, create two properties: parent
and onFontPick
. We need to access the coordinator’s parent (in this case, that is the CustomFontPicker
struct) to be able to dismiss the controller when the user picks a new font. We’ll also reference the onFontPick action handler since we will wire up the delegates in this class. Add these two properties inside of your Coordinator
class:
var parent: SUIFontPicker
private let onFontPick: (UIFontDescriptor) -> Void
Let’s first initialise these two properties. Add this to the Coordinator class:
init(_ parent: SUIFontPicker, onFontPick: @escaping (UIFontDescriptor) -> Void) {
self.parent = parent
self.onFontPick = onFontPick
}
UIFontPickerViewControllerDelegate
is defined with these two optional protocol stubs:
@available(iOS 13.0, *)
public protocol UIFontPickerViewControllerDelegate : NSObjectProtocol {
optional func fontPickerViewControllerDidCancel(_ viewController: UIFontPickerViewController)
optional func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController)
}
We’ll just be using the fontPickerViewControllerDidPickFont method for this tutorial. Start by adding this method into your Coordinator class:
func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) {
}
This method will provide us with a view controller of type UIFontPickerViewController
. This view controller has a property called descriptor. We’ll be constructing a UIFont
with this descriptor and then pass it along to our action handler. Add these two lines into the fontPickerViewControllerDidPickFont
method:
guard let descriptor = viewController.selectedFontDescriptor else { return }
onFontPick(descriptor)
Since the user has now picked the font and we’ve passed it along to our action handler, we can simply dismiss the parent using:
parent.presentationMode.wrappedValue.dismiss()
Therefore, after all of this, the fontPickerViewControllerDidPickFont
method should look like this:
func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) {
guard let descriptor = viewController.selectedFontDescriptor else { return }
onFontPick(descriptor)
parent.presentationMode.wrappedValue.dismiss()
}
Let’s now head back to the parent struct and configure the view controller. Inside of the makeUIViewController
method, add these lines:
let configuration = UIFontPickerViewController.Configuration()
configuration.includeFaces = true
configuration.displayUsingSystemFont = true
let vc = UIFontPickerViewController(configuration: configuration)
vc.delegate = context.coordinator
return vc
Create a makeCoordinator
method to create an instance of the Coordinator class that we just made:
func makeCoordinator() -> CustomFontPicker.Coordinator {
return Coordinator(self, onFontPick: self.onFontPick)
}
That’s it! We’ve successfully configured our bridge. All that is left to do is to test it out in our app.
Back to ContentView
We’ll first create a new @State
property that will be our font. This will be of type UIFont and we’ll cast it to Font inside of our Text’s modifier. Add this property to ContentView:
@State private var customFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
Now, add this modifier to the Text view that will apply our custom font to the text:
.font(Font(customFont))
We’ve cast customFont into Font and so we’ve made SwiftUI happy! All that’s left to do now is to add a sheet modifier to the VStack
to present the CustomFontPicker
view. We’ll also need to add a @State
property to ContentView so we can keep track of the sheet’s presented state. Add this additional property to ContentView
:
@State private var isShowingFontPicker = false
Add a sheet modifier to your VStack
. Use the action handler’s closure to get the descriptor from CustomFontPicker
and assign it to the ContentView’s customFont state property like this:
.sheet(isPresented: $isShowingFontPicker) {
CustomFontPicker { descriptor in
customFont = UIFont(descriptor: descriptor, size: 18)
}
}
Now, we’ll toggle the state of isShowingFontPicker
using our Button. Add this line of code to the Button’s action handler:
isShowingFontPicker.toggle()
We’re all done. Run the application now and you should be able to tap the button to present the CustomFontPicker that will let you pick a custom font. Once picked, this custom font is set as the font style of your Text view. Incredible stuff.
Conclusion
In this tutorial, we have been able to build a bridge to use UIFontPicker inside of a SwiftUI app. I’ve packaged this bridge into a Swift package. You can check it out on GitHub . Thank you for reading through this whole tutorial. If you have any questions, feel free to leave them in the comments section.