Mastodon Development – Josh Hrach

Creating a Menu Button with Subtitles in SwiftUI

SwiftUI menus are extremely useful. When you are showing a Button in a Menu, there are a few ways to style it. However, it may not always be obvious.

For example, let’s say I want to replicate the appearance of this button in the Contacts app. It has a title, a subtitle, and a trailing icon image.

At first glance, you may build something like this:

Menu {
    Button { /* Action */ } label: {
        HStack  {
            VStack {
                Text("work")
                Text("an.email@address.com")
            }
            Image(systemName: "envelope")
        }
    }
} label: { /* The "mail" button */ }Code language: Swift (swift)

However, you’ll quickly see that this is not correct.

While your label in normal circumstances might appear as you’d expect, the menu’s buttons seem to work by mirroring the underlying content. We can imply this by the way the image appears at the trailing edge of the button and not next to the text like our (misaligned?) HStack would suggest.

We see similar behavior if we change our button’s label to use an actual Label type.

Label(
    title: {
        Text("work")
        Text("an.email@address.com")
    },
    icon: { Image(systemName: "envelope") }
)Code language: Swift (swift)

Again, we see the same output as above.

So what can we do differently? If the image is “magically” being picked out and put in the right place, then perhaps there’s other magic happening in the Button. In fact, this turns out to be what is happening. Let’s update our button once more:

Button { /* Action */ } label: {
    Text("work")
    Text("an.email@address.com")
    Image(systemName: "envelope")
}Code language: Swift (swift)

Now we’re not specifying anything about text layout or even where the image should appear. Yet, what is our result?

There is definitely some underlying mirroring happening to pull out each view and put them in their proper place. In fact, if you were to put the Image before the second Text view, you end up with the same output! Thus, this does appear to be what is happening.

In short, if you want to have your menu’s button appear like a contact’s mail or phone buttons, use the following template:

Button { /* Action */ } label: {
    Text("Title")
    Text("Subtitle")
    Image(systemName: /* The icon */)
}Code language: Swift (swift)

UIViewRepresentable: Accessing an underlying view

In the first post of this series, we looked at how we can display a UIView in a SwiftUI hierarchy, as well as how we can change properties on said view. In the second post, we explored options for exposing this view’s delegate to our SwiftUI view, allowing us to provide both an object to act as delegate as well as a way to respond to the delegate directly in the SwiftUI view.

Our demo code is presently:

class ThirdPartyUIView: UIView {
    var shouldAdd: Bool = true
    var delegate: ThirdPartyViewDelegate?
    
    // Same as button tap in view. Results reported via delegate
    func changeInternalValue() { /*...*/ }
}

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    var delegate: ThirdPartyViewDelegate? = nil
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        view.delegate = delegate
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        uiView.shouldAdd = shouldAdd
    }
}

struct ThirdPartyView: View {
    var shouldAdd: Bool
    @State private var coordinator = ThirdPartyCoordinator()
    
    var body: some View {
        ThirdPartyViewRepresentable(delegate: coordinator, shouldAdd: shouldAdd)
    }
}

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    @State private var currentValue = 0
    
    var body: some View {
        Text("\(currentValue)")
        ThirdPartyView(shouldAdd: shouldAdd)
            .viewChangedValueTo { view, newValue in
                currentValue = newValue
            }
        Toggle("Add?", isOn: $shouldAdd)
        Button("Perform Action") {
            // ???
        }
    }
}Code language: Swift (swift)

What does our Button‘s action need to be for us to trigger the ThirdPartyUIView.changeInternalValue() method?

There’s several ways we can solve this. One approach might be to have something trigger the action in the view representable’s updateUIView(_:context:) method. As noted in part one, updating the property on the UIViewRepresentable will trigger that method, so we just need to add properties for that to happen. So we could add a new @State boolean that is passed from our view down into the view representable, and when its value changes, we perform the action. However, this leaves us with a few oddities: To trigger the action, our button has to… toggle a Bool, which is not very obvious; Other developers might be confused as to what its purpose is. It also means that performing actions on the view requires changing state, which isn’t exactly why we have declarative UI frameworks like SwiftUI.

Instead of adding additional (and potentially confusing) state to our views to trigger actions, we really want our button to be able to just call the action that needs to be done. But that requires the action to be exposed in such a way that the Button can access it despite the action and its view living within another view. How can we expose that action in a way that the Button can trigger it? We’ll look at a few first party examples from SwiftUI. First, though, let’s look at how information is passed in SwiftUI.

Two-Way Communication

Normally we pass information from one view to the next by means of the view’s initializer. For instance, providing an initial value for shouldAdd in our example is part of the call site.

ThirdPartyView(shouldAdd: shouldAdd)Code language: Swift (swift)

What if we needed to pass information down through multiple layers of views? That’s where the environment comes into play.

There are two ways of passing information down through the environment.1 The first is by passing in an EnvironmentObject. Essentially, by providing a class object conforming to ObservableObject, you can then listen for said object in a sub-view.

// Creating your object
class SomeObject: ObservableObject { }
let yourObject = SomeObject()

...

// Adding it to the environment of some view
SomeSwiftUIView()
    .environmentObject(yourObject)


struct SomeSwiftUIView: View {
    // Retrieving object from enviroment
    @EnvironmentObject private var yourObject: SomeObject
}Code language: Swift (swift)

The second way is by means of EnvironmentValues. This struct is used anytime you pull information from the environment. Here are a few things you can get from the environment in SwiftUI.

@Environment(\.colorScheme) var colorScheme
@Environment(\.dynamicTypeSize) var typeSizeCode language: Swift (swift)

The Environment wrapper takes a key path to a property on EnvironmentValues. The property we create as a var inherits its type from the property the key path points to. In these examples, both colorScheme and dynamicTypeSize are just properties exposed on the EnvironmentValues struct.

So the environment is used to pass data down the hierarchy. How can we pass information up? That is by means of Preferences.

Whereas you use the environment to configure the subviews of a view, you use preferences to send configuration information from subviews toward their container. However, unlike configuration information that flows down a view hierarchy from one container to many subviews, a single container needs to reconcile potentially conflicting preferences flowing up from its many subviews.

Apple Documentation

To pass information via Preferences, one needs to create a struct conforming to PreferenceKey. This is a named value that is produced by the view. Part of the creation of the PreferenceKey is resolving multiple values for that key into a single value. That single value can then be used by a parent container view.

Now that we’ve checked out the ways we can communicate up and down a SwiftUI view hierarchy, there’s one more thing SwiftUI can teach us. Are there any examples of exposing underlying functionality to a parent container? The answer is actually yes.

Proxies

While the debate of UIKit versus SwiftUI continues among developers, many often forget one simple truth: SwiftUI is quite often just utilizing UIKit under the hood. This is why development techniques that manipulate underlying UIKit views are possible.

This truth, however, leads many to say that SwiftUI is lacking because things that are possible in UIKit are simply not exposed. This is true for elements where tasks might be trigger programmatically but are difficult to describe as a function of state. Let’s consider the ScrollView.

ScrollView {
    Text("First").id(1)
    Text("Second").id(2)
    Text("Third").id(3)
}Code language: Swift (swift)

A ScrollView tells us to put its contents inside of, well, a scroll view. When we create it, we can tell it which axis to scroll on. However, UIScrollView in UIKit does more than that; it allows programmatic scrolling! How did Apple end up adding that functionality?

Introducing the ScrollViewReader.

ScrollViewReader { proxy in
    ScrollView {
        Text("First").id(1)
        Text("Second").id(2)
        Text("Third").id(3)
    }
}Code language: JavaScript (javascript)

The sole purpose of the ScrollViewReader is to expose functionality of underlying scroll views, in this case the ability to scroll to a particular view. It does so by means of a proxy object. We pass that proxy the ID of the object we want to scroll to.

ScrollViewReader { proxy in
    ScrollView {
        Text("First").id(1)
        Text("Second").id(2)
        Text("Third").id(3)
        Button("Scroll To Top") {
            proxy.scrollTo(1)
        }
    }
}Code language: Swift (swift)

If we need to have programmatic control, we use the ScrollViewReader. If we just need a ScrollView without that, we can ignore it. Can we create something similar for our example? Could we end up with something like this?

ThirdPartyReader { proxy in 
    ThirdPartyView()
    Button("Perform Action") {
        proxy.changeInternalValue()
    }
}Code language: JavaScript (javascript)

Creating a Proxy and a Reader

To achieve our goal, we need to create a few new objects. First, our proxy. This object’s purpose is to expose functionality of the ThirdPartyUIView without exposing the entire view itself. The design is fairly simple. 2

class ThirdPartyProxy {
    fileprivate weak var view: ThirdPartyUIView?
    
    func changeInternalValue() {
        view?.changeInternalValue()
    }
}Code language: Swift (swift)

Our proxy has a single method, changeInternalValue(). If there were other functions of ThirdPartyUIView that we’d want to expose, we would add them here.

Now, how will we expose the proxy? We’ll copy the above example and build our own “reader” object.

struct ThirdPartyReader<Content: View>: View {
    private var content: (ThirdPartyProxy) -> Content
    
    init(@ViewBuilder _ content: @escaping (ThirdPartyProxy) -> Content) {
        self.content = content
    }
    
    var body: some View {
        content(proxy)
    }
}Code language: Swift (swift)

The ThirdPartyReader‘s purpose is to provide a proxy instance to our view content. It’s a very simple container! However, how will we get the proxy? And how will the proxy get the view?

Here, there are 2 approaches we could take: (1) We can pass the view or its actions up through Preferences to this container, or (2) pass an object into the Environment that captures what we need.

For now, we’ll go with option 2. Option 1 is definitely possible but has its own quirks. If you build a good example using it, I’d love to hear about it!

Getting the View for Our Proxy

We’re working down through the hierarchy in the Environment. Thus, we start by creating a proxy instance in our new reader.

struct ThirdPartyReader<Content: View>: View {
    @StateObject private var proxy = ThirdPartyProxy()Code language: Swift (swift)

Note that we’re creating this as a StateObject. Our reader is thus owning the instance of this object. However, using this property wrapper requires our proxy conform to ObservableObject. We’ll do so as precedent as a proxy might benefit from having @Published properties depending on the view being wrapped.

With the proxy created and owned by this view, we now pass it into the Environment. However, we’re not just passing it using .environmentObject. Doing so would create a tight requirement for any child view, where we must provide a proxy even if we’re not in a reader.

Instead, we’ll make use of the EnvironmentValues.

extension EnvironmentValues {
    private struct ThirdPartyProxyEnvironmentKey: EnvironmentKey {
        static var defaultValue: ThirdPartyProxy? = nil
    }
    
    var thirdPartyProxy: ThirdPartyProxy? {
        get { self[ThirdPartyProxyEnvironmentKey.self] }
        set { self[ThirdPartyProxyEnvironmentKey.self] = newValue }
    }
}Code language: Swift (swift)

We first create a new EnvironmentKey. It tells us the type to be passed into the environment which will be an optional ThirdPartyProxy. By default, it’ll be nil, which means no proxy exists in the environment.

We’ll use the above to pass our new proxy into the environment of the reader’s view.

var body: some View {
    content(proxy)
        .environment(\.thirdPartyProxy, proxy)
}Code language: Swift (swift)

Now, any view content provided to the reader container will find a proxy object in the environment. We can now check the environment in our ThirdPartyView and update our view representable to set the view on that proxy.

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    @State private var actionTriggered = false
    
    var delegate: ThirdPartyViewDelegate? = nil
    var proxy: ThirdPartyProxy?
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        view.delegate = delegate
        proxy?.view = view
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        uiView.shouldAdd = shouldAdd
    }
}
Code language: Swift (swift)
struct ThirdPartyView: View {
    @Environment(\.thirdPartyProxy) private var proxy
    
    var shouldAdd: Bool
    @State private var coordinator = ThirdPartyCoordinator()
    
    var body: some View {
        ThirdPartyViewRepresentable(delegate: coordinator, proxy: proxy, shouldAdd: shouldAdd)
    }
}
Code language: Swift (swift)

With the above changes, we’ve added the last piece and have enabled our SwiftUI views to access underlying methods of our wrapped UIView. Our fully functional sample view now looks like this:

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    @State private var currentValue = 0
    
    var body: some View {
        ThirdPartyReader { proxy in
            Text("\(currentValue)")
            ThirdPartyView(shouldAdd: shouldAdd)
                .viewChangedValueTo { view, newValue in
                    currentValue = newValue
                }
            Toggle("Add?", isOn: $shouldAdd)
            Button("Perform Action") {
                proxy.changeInternalValue()
            }
        }
    }
}Code language: Swift (swift)

In these posts, we’ve looked at how we can fully use a UIView within a SwiftUI hierarchy. To do so, we’ve simply used what Apple has provided since SwiftUI was introduced with iOS 13. And while SwiftUI might be the future of building apps, it will likely continue to be built on top of UIKit, and UIKit likely has a long future ahead of it. Being able to work with UIKit in SwiftUI is just another tool in our ever growing toolbox as developers on Apple platforms.

The full sample code is available as a Github Gist.

  1. When speaking about the environment and its usage, I’ll be referring to APIs and terminology from before iOS 17. iOS 17 has made some changes to Environment and includes the new Observable framework, neither of which I’ll be discussing here. ↩︎
  2. This variable is declared as fileprivate just so that the view property is exposed to the other classes in this file but not to the consumers of the API we’re building. If you’re adding this into a framework or library, then keeping this property internal while making the functions you add public will satisfy this requirement, too.

    We also make this weak so that it doesn’t retain the view it is referencing. ↩︎

UIViewRepresentable: Working with delegates in SwiftUI

In the first post of this series, we looked at how we can display a UIView in a SwiftUI hierarchy, as well as how we can change properties on said view. As a refresher, here is our example.

Imagine we have a third party SDK that provides their functionality in a pre-packaged UIView subclass. We’ll call this ThirdPartyUIView. It has properties set on it to change its behavior, methods that can be called on it, and provides internal feedback by means of a delegate object.

class ThirdPartyUIView: UIView {
    var shouldAdd: Bool
    var delegate: ThirdPartyViewDelegate?
    
    // Same as button tap in view. Results reported via delegate
    func changeInternalValue() { /*...*/ }
}

protocol ThirdPartyViewDelegate {
    func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int)
}Code language: Swift (swift)

And here is our UIViewRepresentable view thus far.

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        uiView.shouldAdd = shouldAdd
    }
}Code language: Swift (swift)

How can we provide a delegate when creating our view? Let’s look at a few approaches.

The Coordinator

The UIViewRepresentable protocol gives us one approach that we can take. The clue is found in the Context object that is provided in both the makeUIView(context:) and updateUIView(_:context:) methods. It has a coordinator property of type Coordinator. But what is this type?

Here is how it is defined in the UIViewRepresentable protocol:

/// A type to coordinate with the view
associatedtype Coordinator = VoidCode language: Swift (swift)

By default, Coordinator is type Void, essentially meaning an empty type. It is provided to the Context by means of the protocol method makeCoordinator(). By providing our own type here, we can create an object that will coordinate with the view.

Let’s use that to create a type conforms to the ThirdPartyViewDelegate protocol. We can then create an instance of it and provide it when we initialize our view.

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    class Coordinator: ThirdPartyViewDelegate {
        func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int) {
            // Respond to newValue
        }
    }
    
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        view.delegate = context.coordinator
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        uiView.shouldAdd = shouldAdd
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
}
Code language: Swift (swift)

Just like that, we’ve been able to define and provide a delegate that can respond to changes to the ThirdPartyUIView!

However, we have some limitations here. First, this means that the delegate is defined and created entirely within the ThirdPartyViewRepresentable. There isn’t any way to provide a different delegate. We also have no way to allow this delegate to interface with other elements, such as some kind of UI within our SwiftUI hierarchy. How can we allow that?

Injecting a Delegate

The simplest way is by creating our delegate class outside of the view representable and providing an instance of it when we initialize the view.

class OurDelegate: ThirdPartyViewDelegate {
    func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int) {
        // Respond to newValue
    }
}

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    var delegate: ThirdPartyViewDelegate? = nil
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        view.delegate = delegate
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) { ... }
}
Code language: Swift (swift)

Now, when we make use of the view representable, we have the option of providing a delegate.

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    private let delegate = OurDelegate()
    
    var body: some View {
        ThirdPartyViewRepresentable(delegate: delegate, shouldAdd: shouldAdd)
        Toggle("Add?", isOn: $shouldAdd)
    }
}
Code language: Swift (swift)

This is great because we can provide any delegate we want for the underlying view. If we use this view in multiple places or in multiple projects, we can create a different delegate based on each situation. However, we still don’t have a way for these updates to be exposed in our SwiftUI view. For instance, what if we wanted a Text view to display the new value? How could we change our view to support that?

To answer, let’s first take a step back and consider how we might want our code to be written.

Planning Our API

As we build our own implementations, it’s sometimes helpful to look at existing first party code. For example, let’s look at how SwiftUI exposes various actions that we can respond do.

One example is .onAppear(perform:), which lets us declare a closure that is called when the view appears. This sounds like what we want to emulate. Providing a closure of our own to perform when a delegate method is invoked would be a good way to hook into the delegate while letting us access the data in our SwiftUI view. So our destination is something like this:

// What we'd like to build
ThirdPartyView()
    .viewChangedValueTo { thirdPartyView, newValue in 
        // Use newValue to update some state in our view
        // Access <span style="white-space: pre-wrap;">thirdPartyV</span>iew if necessary
    }

Code language: Swift (swift)

What do we need to do to create the above?

To begin, let’s first add another layer of abstraction around our view representable.

struct ThirdPartyView: View {
    var shouldAdd: Bool
    
    var body: some View {
        ThirdPartyViewRepresentable(delegate: ???, shouldAdd: shouldAdd)
    }
}Code language: Swift (swift)

Like before, we will initialize this view with a shouldAdd property so we can control it externally. But what about our delegate? What should we put there? We’ll need to create a class that can act as a ThirdPartyViewDelegate that takes the data from the delegate method and forwards it to a closure that we define.

class ThirdPartyCoordinator: ThirdPartyViewDelegate {
    var viewChangedValueTo: ((ThirdPartyUIView, Int) -> Void)?
    
    func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int) {
        viewChangedValueTo?(view, newValue)
    }
}Code language: Swift (swift)

When the delegate method is called, it’ll pass on its parameters to the viewChangedValueTo closure. We will now use the above coordinator as our representable’s delegate.

struct ThirdPartyView: View {
    var shouldAdd: Bool
    @State private var coordinator = ThirdPartyCoordinator()
    
    var body: some View {
        ThirdPartyViewRepresentable(delegate: coordinator, shouldAdd: shouldAdd)
    }
}

Code language: Swift (swift)

Note two things. First, we declare this private; the existence of the coordinator is purely an implementation detail of ThirdPartyView and not something that would need to be changed from the outside. Second, we declare it as a @State property. Though we won’t be changing its value, it now ties the existence of the coordinator along with the view. That will be important for what we do next.

Next, we’ll create our view modifier. However, we wouldn’t want it to work with all Views; this is only important if we’re using a ThirdPartyView. Well, looking again at SwiftUI, certain views, such as Image, come with their own modifiers. Instead of being declared as extensions on View, they exist as extensions of a particular type. We’ll do the same and create this view modifier in an extension of our view. Its purpose is to allow us to set the closure that will be called when the delegate method is hit.

extension ThirdPartyView {
    func viewChangedValueTo(_ closure: @escaping (_ view: ThirdPartyUIView, _ newValue: Int) -> Void) -> Self {
        coordinator.viewChangedValueTo = closure
        return self
    }
}Code language: Swift (swift)

With this extension, we provide a closure that is called when the delegate method is called. If there were other methods as part of the delegate protocol, we could likewise expose them to SwiftUI in the same manner.

One additional change I like doing here is adding support for providing an entire delegate object to respond to these delegate calls. To do that, we’ll modify our coordinator to hold another delegate to forward calls to and add a view modifier that lets us set that delegate.

class ThirdPartyCoordinator: ThirdPartyViewDelegate {
    var viewChangedValueTo: ((ThirdPartyUIView, Int) -> Void)?
    var externalDelegate: ThirdPartyViewDelegate?
    
    func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int) {
        viewChangedValueTo?(view, newValue)
        externalDelegate?.view(view, changedValueTo: newValue)
    }
}

extension ThirdPartyView {
    func setViewDelegate(_ delegate: ThirdPartyViewDelegate) -> Self {
        coordinator.externalDelegate = delegate
        return self
    }
}Code language: Swift (swift)

With this addition, not only can we respond to delegate methods directly in our SwiftUI view, but we also can provide a delegate class to handle those as well.

The ThirdPartyUIView, its properties, and its delegate are now usable in SwiftUI like so.

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    @State private var currentValue = 0
    
    // Use our delegate for something like logging all delegate usage
    private let delegate = OurDelegate()
    
    var body: some View {
        Text("\(currentValue)")
        ThirdPartyView(shouldAdd: shouldAdd)
            .viewChangedValueTo { view, newValue in
                currentValue = newValue
            }
            .setViewDelegate(delegate)
        Toggle("Add?", isOn: $shouldAdd)
    }
}Code language: Swift (swift)

ThirdPartyView communicates with our underlying ThirdPartyUIView via a view representable internally. The view’s shouldAdd property is exposed and can be changed by our Toggle. And we have full access to the view’s delegate by providing an object conforming to the delegate protocol or by providing closures via view modifiers for the specific delegate methods we want to work with.

However, there’s still one piece missing. Let’s add one more piece to the view:

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    @State private var currentValue = 0
    
    private let delegate = OurDelegate()
    
    var body: some View {
        Text("\(currentValue)")
        ThirdPartyView(shouldAdd: shouldAdd)
            .viewChangedValueTo { view, newValue in
                currentValue = newValue
            }
            .setViewDelegate(delegate)
        Toggle("Add?", isOn: $shouldAdd)
        Button("Perform Action") {
            // ???
        }
    }
}
Code language: Swift (swift)

Here, we’ve added a Button. Why? Simple: we want to trigger the view’s action programmatically! But how can we expose the view’s action to make it accessible to our Button?

UIViewRepresentable: Showing UIKit components in SwiftUI

SwiftUI was unveiled to the world at WWDC in 2019. Since its introduction, Apple has offered a way for SwiftUI to bring in UIKit components. How does that work? And how can we build solutions to communicate from UIKit to SwiftUI and vice versa? This multi-part series will look into answers to those questions one piece at a time.

Our Example

Imagine we have a third party SDK that provides their functionality in a pre-packaged UIView subclass. 1 We’ll call this ThirdPartyUIView. It has properties set on it to change its behavior, methods that can be called on it, and provides internal feedback by means of a delegate object. Let’s use this as our very basic example:

class ThirdPartyUIView: UIView {
    var shouldAdd: Bool
    var delegate: ThirdPartyViewDelegate?
    
    // Same as button tap in view. Results reported via delegate
    func changeInternalValue() { /*...*/ }
}

protocol ThirdPartyViewDelegate {
    func view(_ view: ThirdPartyUIView, changedValueTo newValue: Int)
}Code language: Swift (swift)

As you can see, our example ThirdPartyUIView has a boolean that can change internal behavior to either add or subtract a value, as well as a delegate that is notified whenever this internal state (we’re using an Int as an example) is changed. Lastly, we’ll assume this view has a button that changes this internal value, and the action performed by that button is exposed for us to call programmatically.

With this defined, let’s explore how we can work with this view and all of its functionality in SwiftUI.

Displaying a UIView

First, how do we get a UIView to appear in SwiftUI? This is where UIViewRepresentable can help us. 2 We declare a struct that conforms to this type. Its purpose is to provide the underlying UIView type that we want to wrap and display, as well as functions for creating and updating said view.

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        
    }
}Code language: Swift (swift)

With the code above, we can now use our ThirdPartyUIView in a SwiftUI hierarchy. For example:

struct ThirdPartyDemoView: View {
    var body: some View {
        ThirdPartyViewRepresentable()
    }
}Code language: Swift (swift)

If we loaded this view, we would see our ThirdPartyUIView within our SwiftUI application. However, because we haven’t exposed any functionality, we can’t really do anything with the view yet. Let’s change that.

Updating View Properties

Let’s start with changing properties on the underlying view. As you’ll recall above, our ThirdPartyUIView includes a shouldAdd property. If true, each action performed by the view will add one to the internal value; false will have it subtract instead.

First, we want to allow this option to be set when we create the view. To do so, we need to provide it when creating our view representable.

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd 
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        
    }
}
Code language: Swift (swift)

Now, when we create our view, we can provide a value that will be used when initializing the view. As this is something we might want to change as we use it, we’ll add it as a @State property on our view along with a Toggle that will let us change it as needed.

struct ThirdPartyDemoView: View {
    @State private var shouldAdd = true
    
    var body: some View {
        ThirdPartyViewRepresentable(shouldAdd: shouldAdd)
        Toggle("Add?", isOn: $shouldAdd)
    }
}
Code language: Swift (swift)

However, we’ve now got an issue. If you change the value of the Toggle, behavior of the view doesn’t change! Why?

When the value of shouldAdd changes, the underlying view is not recreated, so makeUIView(context:) is not called again. Rather, the view is updated. Thus, we need to update the property inside of the updateUIView(_:context:) method. 3

struct ThirdPartyViewRepresentable: UIViewRepresentable {
    var shouldAdd: Bool
    
    func makeUIView(context: Context) -> ThirdPartyUIView {
        let view = ThirdPartyUIView()
        view.shouldAdd = shouldAdd
        return view
    }
    
    func updateUIView(_ uiView: ThirdPartyUIView, context: Context) {
        uiView.shouldAdd = shouldAdd
    }
}
Code language: Swift (swift)

With the above addition, when we toggle the state of shouldAdd, the view now updates and behaves as expected.

Now that we can change properties on the underlying UIView, let’s look at how we can expose the ThirdPartyViewDelegate.


  1. While this series of posts will explore exposing and working with a third party’s UIView in our own SwiftUI views, these tips can also apply to taking your own existing UIKit code and using it in SwiftUI yourself. ↩︎
  2. When working with UIKit, we also have UIViewControllerRepresentable. On the Mac with AppKit, NSViewControllerRepresentable and NSViewRepresentable exist as counterparts. This post talks exclusively about UIViewRepresentable but the principles will apply to the other representable options as well. ↩︎
  3. While we’re working with a single property, there can be some gotchas when dealing with multiple properties, or when trying to update parts of a view that make cause state changes. Chris Eidhof has a great post with some examples of these gotchas. ↩︎

iOS Widgets, Loops, and Memory

So, you’re building a widget for your iOS application. Did you know that they can stop working properly if they use up too much memory? It’s true!

I found myself unknowingly in a memory usage hole. The system will take an archived snapshot of your view to display for a given point in time. However, apparently it is possible to have it fail to work properly if your view uses up too much memory when producing the snapshot.

Imagine you have a data source with a list of items you want to display.

let widgetData = yourDataSource.getLatestData()
print(widgetData.count) // 20Code language: Swift (swift)

You want to display a list of your data items in your widget. But because you don’t know how much space you have to use, you want to show the maximum number of items that can fit. So you think you’re clever and try something like this in your Widget’s view.

func viewForData(_ yourData: YourDataType) -> some View { ... }

@ViewBuilder
func buildDataStack(count: Int) -> some View {
  VStack {
    ForEach(entry.widgetData.prefix(count)) { yourData in 
      viewForData(yourData)
    }
  }
}

var body: some View {
  VStack {
    ViewThatFits(in: .vertical) {
      Group {
        ForEach((1..<20).enumerated().reversed(), id: \.element) { _, element in 
          buildDataStack(count: element)
        }
      }
    }
  }
}
Code language: Swift (swift)

Well, let’s see what we have here. First, we’re defining a function that returns a view for your data model. Then we have a function that builds a stack of those views depending on the count provided. And then, in our view body, we’re just looping through from 20 down to 1 to see the largest group of items that’ll appear in our widget. That sounds like a neat solution to our initial problem!

Except that, because of the memory used in creating all of those iterations, we’ve now run out of memory for our Widget and it will fail to produce a valid snapshot! We have essentially _broken_ our widget!

If you’re going to try something similar to the above, make sure you do it with a smaller range of values. Trying to build 20 separate lists to see what fits will likely cause issues. Trying 5 times may be more successful.

Non-Selectable Rows in a SwiftUI Picker

With SwiftUI, it’s quite easy to create forms to collect user input. Consider the following.

struct ContentView: View {
    @State var numberOfCookies: Int?
    
    var body: some View {
        NavigationView {
            Form {
                Picker("How many cookies?", selection: $numberOfCookies) {
                    ForEach(1..<10) { number in
                        Text("\(number)")
                            .tag(number as Int?)
                    }
                }
            }
            .navigationTitle("Order")
        }
    }
}Code language: PHP (php)

Being in a Form in a NavigationView means our Picker above will navigate to a list on tap by default. We thus get a view that looks like this.

If they tap on the row, they’re shown this list of options.

The Picker will take each view generated in the ForEach and show it as an option.

This is super simple! However, what if we wanted to show some kind of header alongside our various options? Or perhaps we wanted to section the options? Or maybe we’re displaying options from a third-party API that includes such headers or section names in the returned data? To figure that out, let’s first understand how the SwiftUI picker works.

How Picker Works

The above code seems fairly straightforward. I defined a Picker in a hierarchy. It had a title that is displayed in the form. The selection was set as a binding to a local state variable. The trailing closure was my picker content, where I used a ForEach to generate each option.

However, as simple as that might be, you might be asking: What is with the tag?

Each view that is used in the Picker needs to be tagged with the value it represents. In our case, it’s just a number. So I am tagging each generated view so that it corresponds with a valid value.

But why didn’t I just say “.tag(number)“? It’s because it wouldn’t match the type the Picker is looking for, namely the optional Int of the state variable. If I had done that, the Picker would still show each of my numeric options, but none of them would be selectable. Tapping on one wouldn’t change the data source.

This leads to an interesting observation: Any view in the Picker that doesn’t have a tag matching the bound type is thus non-selectable.

Making Picker Headers

With this knowledge in mind, let’s go back to our example. Let’s say we wanted to show that we have a discount if the customer buys more than 4 cookies. How can we do it?

We change our picker like this.

Picker("How many cookies?", selection: $numberOfCookies) {
    Text("Normal Price")
        .font(.headline)
    
    ForEach(1..<5) { number in
        Text("\(number)")
            .tag(number as Int?)
    }
    
    Label("Discount", systemImage: "tag")
        .font(.headline)
    
    ForEach(5..<10) { number in
        Text("\(number)")
            .tag(number as Int?)
    }
}Code language: PHP (php)

We now have other views in our picker that are not tagged as options, thus becoming visible yet non-selectable display items. In the above code, we first display “Normal Price” before showing options for quantities up to 4. We then show a more complex view, a Label, before showing the other quantities.

Our view now looks like this.

And just like that, we have support for displaying non-selectable content within our picker.

Using a Binding in SwiftUI – Simple Example

In my original post on Bindings in SwiftUI, I had mentioned when we might want to use a binding:

You would use a binding to create a two-way connection between a property that stores data, and a view that displays and changes the data.

I showed an example of how we could bind one of our local @State properties to a Toggle. The Toggle could update the state of our property through it.

We can use other system controls, such as TextField or Picker, to also update our local variables. But what if we want to have another one of our views update a variable owned by another? Let’s solve a simple example and, in doing so, we’ll see when we will make use of @Binding.

I have a simple view that displays my name. I am storing it locally as a variable.

struct NameDisplayView: View {
    var name: String = "unknown"
    
    var body: some View {
        Text("Your name is \(name)")
    }
}Code language: JavaScript (javascript)

It simply takes the text there and outputs "Your name is unknown". It works, but I want to be able to update that name so I can have it show me and not assume I have no name.

Well, we know how we can bind to something from our original post. So let’s make a view where we can change some text.

struct NameChangeView: View {
    @State var text: String
    
    var body: some View {
        TextField("Type Here", text: $text)
    }
}Code language: JavaScript (javascript)

So far, this is a simple example. If we loaded up NameChangeView, we could tap into the TextField and, as we type, text would be updated. Why? Because we have it bound to the text property on TextField, which is of type Binding<String>. So as the TextField updates the text, our view can get the latest values.

But let’s say we want to present this view as a sheet, let the user update the text, and then send it back to our original view. Can we do it? Yes!

What do we need to change so that our NameChangeView can pass changes back from itself to another view? All we have to do is change @State to @Binding. With that change, we now have two way communication via that variable.

Let’s add a little styling and a way to dismiss the view. That gives us our final form:

struct NameChangeView: View {
    @Environment(\.presentationMode) var presentationMode
    @Binding var text: String
    
    var body: some View {
        TextField("Type Here", text: $text, onCommit: { self.presentationMode.wrappedValue.dismiss() })
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .padding()
    }
}Code language: PHP (php)

Now, how can we use this in our original view?

We’ll add a button to trigger a sheet. This happens by binding a local Bool to the .sheet(isPresented:) modifier. Lastly, we add our NameChangeView as what is being presented modally as a sheet. We pass our local name variable as a binding to that view, so it becomes $name.

struct NameDisplayView: View {
    @State var name: String = "unknown"
    @State var showNameChange = false
    
    var body: some View {
        VStack {
            Text("Your name is \(name)")
            Divider()
            Button("Change") {
                self.showNameChange = true
            }
        }
        .sheet(isPresented: $showNameChange) {
            NameChangeView(text: self.$name)
        }
    }
}Code language: PHP (php)

Now, we can tap on “Change”, which will present our text field. We can tap on it, change our name, hit “Return”, and be taken back to our original view but with our name in place.

Bindings might seem like a new concept to iOS developers, but they open up a lot of possibilities in Swift. Explore various ways that you can use bindings to communicate between views. You’ll find them very useful, especially in cases of user input as we saw in our example.

You can find the example code in this Github Gist.

Bindings in SwiftUI

Property wrappers were one of the recent additions to Swift. SwiftUI makes use of several very useful property wrappers. One of them is @Binding. The @Binding property wrapper is really just a convenience for the Binding<T> type.

But before we go into using bindings, what are bindings?

Per Apple’s documentation, a binding connects a property to a source of truth stored elsewhere, instead of storing data directly. You would use a binding to create a two-way connection between a property that stores data, and a view that displays and changes the data.

In SwiftUI, you’ll find this to be a fairly common pattern, especially with built in input fields.

Binding Example

Let’s say we want a view with a switch for the user to interact with. How do we build it?

struct BindingExample: View {
    @State var switchIsOn = false

    var body: some View {
        Form {
            Toggle(isOn: $switchIsOn) {
                Text("Switch 1")
            }
        }
    }
}Code language: PHP (php)

First, we need a mutable variable to hold the state of our switch. In this case, we have a Bool, named switchIsOn. We have to use the @State wrapper so it can be modified in our view.

Next, when we build our Toggle, we need to provide a binding to a property that will hold the state of the toggle. When we pass in the variable, we use the dollar sign to denote that we’re binding to it. So our view holds the current state of switchIsOn, while the Toggle has a binding internally. When the state of the Toggle flips, it is propagated through $switchIsOn back into our view.

Creating a manual binding

If we again look at Apple’s description of a binding, we see that it involves a two-way connection. The binding needs to know the value to get, and where to set the new value when it is changed.

With that knowledge, we can look at how Binding workings. Let’s look at our example again. This time, we’re going to make a binding ourselves.

struct BindingExample: View {
    @State var switchIsOn = false 

    var body: some View {
        Form {  
            Toggle(isOn: Binding(get: {
                self.switchIsOn
            }, set: { newValue in
                self.switchIsOn = newValue
            }) ) {
                Text("Also Switch 1")
            }
        }
    }
}Code language: JavaScript (javascript)

Binding has an initializer that takes a couple of arguments. One, with the get label, is a closure explaining where the binding gets its value. In our example, this is our switchIsOn variable. The other, set, is given the new value for the binding. This gives us the opportunity to do something with it now that it has changed. In our case, we’re setting switchIsOn to the new value.

In its most basic form, Binding opens up a lot of interesting possibilities. In later posts, I’ll talk about when we might want to create a binding between our views, as well as some interesting problems a manual binding can solve.

Property Wrappers in Swift

With Swift 5.1 and Xcode 11, many new features came to Swift. A lot of these were built in to help enable SwiftUI in Apple’s latest OS releases. While diving into SwiftUI is a great way to understand those features, there are some that we don’t need to go into SwiftUI to understand. One of these is property wrappers.

I like to explain property wrappers by a literal definition of the phrase: They wrap properties in functionality. While there are some built into various Apple frameworks (ie SwiftUI), it’s easy to create your own, and they can give you a lot of neat functionality.

How to create a property wrapper

To define a property wrapper, you need to have a custom type (I typically use structs) that you mark as a property wrapper with the @propertyWrapper keyword.

@propertyWrapper
struct Wrapper { 

}Code language: CSS (css)

By adding that attribute, my type now must conform to its requirements. To do so, I need to add a non-static property named wrappedValue. The type of this property will depend on the kind of wrapper you are building. Let’s look at some practical examples and see what we can build.

Easily decoding an ISO 8601 Date

Since Swift 3, we’ve had the very convenient Encodable and Decodable protocols. The type alias Codable is how we typically see it. Having our models conform to Codable allows us to easily encode and decode our models to and from JSON for API communication.

The Date type conforms to Codable. But how does it encode/decode by default?

struct DateTest: Codable {
    var date: Date = Date()
}

let dateTest = DateTest()
let data = try JSONEncoder().encode(dateTest)
let json = try JSONSerialization.jsonObject(with: data, options: [])

print(json)
// {
//    date = "608081487.019236";
// }Code language: JavaScript (javascript)

Okay. At least Date can encode automatically. I can live with that. But it looks like it’s turning a date into a number. What if we want to work with different standard, such as an ISO 8601 Date?

What is an ISO 8601 Date? It a popular date format presented as a string where a date like 10:43 pm on April 4, 2020 would appear like this:
2020-04-08T22:43:30+00:00

So can Date decode this by default? Let’s find out!

let isodateJSON = """
{
    "date": "2012-07-14T01:00:00+01:00"
}
"""

let isoDecodeTest = try JSONDecoder().decode(DateTest.self, from: isodateJSON.data(using: .utf8)!)Code language: PHP (php)

So using a multi-line statement in Swift, I’m going to try and decode an instance of our DateTest struct with this JSON object. Will this work? If you saw how Date encoded itself, then you won’t be surprised by the response we get from trying to decode this JSON.

ERROR: typeMismatch(Swift.Double, 
    Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "date", intValue: nil)], 
    debugDescription: "Expected to decode Double but found a string/data instead.", 
    underlyingError: nil))Code language: JavaScript (javascript)

While we have a valid date format, Date is expecting a Double. What can we do?

We could create a custom JSONDecoder when decoding our type. But if we want to support ISO 8601 dates across various models, then we have to create custom decoders for all of them. That could end up being a lot of manual decoding work. OR… we could create a property wrapper to automatically decode the string coming from our JSON into a valid Date.

We start by creating our property wrapper. Ultimately, we want to provide a Date. So that will be our wrapped type.

@propertyWrapper
struct ISO8601Date {
    var wrappedValue: Date
}Code language: JavaScript (javascript)

This satisfies the basic requirements of @propertyWrapper. Now we have to add our own functionality. In our case, here is where we will build our custom encoding and decoding capabilities.

We start by declaring ISO8601Date itself as conforming to Codable. This will let us wrap properties in a Codable-conforming model and not cause any errors about a model needing to manually provide such conformance.

Next, we create our custom Codable conformance within our ISO8601Date struct.

init(from decoder: Decoder) throws {
    let value = try decoder.singleValueContainer()
    let stringValue = try value.decode(String.self)
    if let date = ISO8601DateFormatter().date(from: stringValue) {
        wrappedValue = date
    } else {
        throw DecodingError.typeMismatch(Date.self, DecodingError.Context(codingPath: [], 
                        debugDescription: "Failed to decode ISO Date. Invalid string format."))
    }
}

func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    
    let string = ISO8601DateFormatter().string(from: wrappedValue)
    try container.encode(string)
}
Code language: JavaScript (javascript)

Our initializer will try and decode a string. If this string can be used to decode a date in the ISO 8601 standard format, it will do so. Otherwise, it will throw an error. Meanwhile, our custom encode function takes the stored date and encodes it into a string using the ISO8601DateFormatter.

Lastly, we add a simple init to allow the user to set a default value with our property wrapper.

init(wrappedValue: Date) {
    self.wrappedValue = wrappedValue
}Code language: JavaScript (javascript)

Put all of this together and what do we get? If we look at our original DateTest model, we can now prepend the @ISO8601Date attribute to our date property and it will encode to and decode from an ISO 8601 string automatically.

struct DateTest: Codable {
    @ISO8601Date var date: Date = Date()
}Code language: JavaScript (javascript)

Using UserDefaults for property storage

You may be using UserDefaults in your app to store simple properties. Perhaps it’s a Bool saying if a user has enabled or disabled some feature. Or perhaps you’re storing a String of the last logged in user. You may have built convenience properties to read and write these values. In doing so, you may have ended up with a pattern like this:

class SettingManager {
    static var userViewedWhatsNew: Bool {
        get {
            UserDefaults.standard.bool(forKey: "userViewedWhatsNew")
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "userViewedWhatsNew")
        }
    }
    
    static var lastLoggedInUser: String? {
        get {
            UserDefaults.standard.string(forKey: "lastLoggedInUser")
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "lastLoggedInUser")
        }
    }
}Code language: JavaScript (javascript)

There’s nothing wrong with the above code (except maybe hard-coded key values). But as you add support for more and more properties, things get to be real tedious. So if we were to abstract this out, what pattern do we see?

For each property, we are both (a) getting the value from UserDefaults and (b) setting the value to UserDefaults using the same (c) key. With this pattern in mind, let’s approach our property wrapper.

@propertyWrapper
struct UserDefault<StoredType> {
    public let key: String

    var wrappedValue: StoredType {
        get {
            UserDefaults.standard.object(forKey: key) as! StoredType
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}Code language: JavaScript (javascript)

Instead of using wrappedValue to store a value we initialize, like in our first example, we have a computed getter and setter. It is performing our reading and writing with UserDefaults. The key used for those transactions? We provide it in our property wrapper.

What data type does this work with? You’ll see that we are using a generic type (StoredType). It will inherit the type from whatever you end up wrapping.

But wait! What if you wrap an optional type, like String?. Won’t the getter crash if there is no object for key? Nope! Because it’s coming back as an optional, or Optional.none case (if you look at how Optionals work in Swift), which is a valid value for something of type String?. But if we did try to read a String that wasn’t there, then it would crash.

So how about we add a default value?

@propertyWrapper
struct UserDefault<StoredType> {
    public let key: String
    public let defaultValue: StoredType

    var wrappedValue: StoredType {
        get {
            UserDefaults.standard.object(forKey: key) as? StoredType ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}Code language: JavaScript (javascript)

Now we will attempt to return a value of type StoredType from UserDefaults but, in the event no such object exists, we will return our defaultValue.

How can we use this in our SettingManager above?

class SettingManager {
    @UserDefault(key: "userViewedWhatsNew", defaultValue: false) 
    static var userViewedWhatsNew: Bool    

    @UserDefault(key: "lastLoggedInUser", defaultValue: nil) 
    static var lastLoggedInUser: String?
}Code language: JavaScript (javascript)

If we were to add support for a new property stored in UserDefaults, it is super simple.

Now, notice how we are setting the key and defaultValue properties when declaring our property wrapper. This is making use of the synthesized initializer on our struct. You could make your own init function and use it when declaring the property wrapper.

Summary

Property wrappers are super helpful. They can help you take repeated code and patterns away from your properties and move them into a reusable component. As we’ve seen here, you can make various Codable edge cases easy to solve. They can also help you with code you find yourself repeating in things like a set, didSet, or other such case.

Like other Swift features, property wrappers are just another tool that are worth having in your developer tool box. I hope this post helped demystify them a little bit and make them more approachable for you.

You can find the code for this post in this Github Gist.

Swift Protocols Aren’t Scary

For people new to Swift, or coding in general, there are some keywords that might seem daunting. Optionals. Generics. Sometimes you can explain these in a simple way. Other times, exposure to them might scare learners.

Protocols might seem like a big concept for some. If you look at how many protocols exist in various frameworks, you might think, “Wow, that’s a lot to learn! When will I use all of these?”

For instance, the Int type conforms to 11 different protocols. Do you need to know them all?

What is a protocol?

A protocol is essentially a blueprint. It defines functionality. This can be requiring the existing of certain properties or functions. Something that conforms to a protocol is required to provide the required elements.

For instance, I could declare a protocol that requires the conforming object, whether a class, struct, or enum, to have a name property.

protocol Nameable {
    var name: String { get set }
}Code language: JavaScript (javascript)

If I have a struct representing, say, an instrument, I can have it conform to that protocol, which will require the presence of a name property.

struct Guitar: Nameable {
    var type: String
    
    var name: String // Required because of protocol
}Code language: JavaScript (javascript)

Protocol conformance can be declared when you create your type, or it can be done in an extension. However, extensions cannot contain stored properties, so you can only add support for required functions in extensions.

protocol DataSource {
    func numberOfItems(in section: Int) -> Int
    func dataToShow(at indexPath: IndexPath) -> Data
}

class DataManager {
    // Your class stuff here
}

extension DataManager: DataSource {
    func numberOfItems(in section: Int) -> Int {
        // Return the number of items in each section
    }
    
    func dataToShow(at indexPath: IndexPath) -> Data {
        // Return some data to show for each row/section
    }
}Code language: PHP (php)

When would I use one?

You would use a protocol when you want to require functionality for a type. When would you use this?

With the exception of very basic iOS applications, you are likely to come across a protocol and not even realize it. Consider the following example:

class MyTableViewController: UIViewController {
    
    var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
    }
}

Here, we have a view controller that we’ve created. It has a table view in it. To have a working table view, we need to have a data source and delegate. In simple examples, you would see what we have here: the class itself conforms as a datasource and delegate.

To do that, we’d have to have our class conform to 2 different protocols. One tells the requirements for the data source, the other tells the requirements to be a delegate.

extension MyTableViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Return number of rows in each section
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Return the table view cell for each part of the table view
    }
    
    
}

extension MyTableViewController: UITableViewDelegate {
    // Add support for the delegate methods you want to support
}
Code language: JavaScript (javascript)

So here, we’ve told our class to conform to two protocols so that it can satisfy the requirements of the table view.

They’re just blueprints

When we look at the above example of the UITableView, when it’s looking for something to be a data source, it’s wanting something that conforms to UITableViewDataSource. It’s basically saying, “If you conform to these blueprints, you, too, can be a data source!”

Don’t look at the presence of ALL protocols available to use as daunting. Remember: it’s just saying there are dozens of blueprint options out there. But they’re only used for specific reasons. UITableView wants its data source to conform to a certain protocol. UIScrollView’s delegate has certain requirements.

And what about what you might make? Would you ever want to make a protocol? Sure. If you ever want to require your types have certain properties or functions, you would create your own protocol. You’d basically be drafting your own blueprints in Swift.

That’s all protocols are. Blueprints. They’re not scary at all.