Swift Identifiable: How SwiftUI Identify Data in its List Views?

In this blog post, I will explore the Swift Identifiable protocol and how it plays a crucial role in identifying data within SwiftUI views. SwiftUI, Apple’s modern declarative framework for building user interfaces, brings a fresh approach to handling data and views. One of its key features is the ability to efficiently manage and update views based on their unique identifiers.

In this tutorial, I will guide you through the process of implementing the Identifiable protocol in your data models, explaining its significance in SwiftUI views. You will explore various scenarios where SwiftUI utilizes identifiable, such as working with SwiftUI List, ForEach, and Picker.

By the end of this tutorial, you will have a solid understanding of how SwiftUI identifies data using the Identifiable protocol and how you can leverage this powerful feature in your own SwiftUI projects. So, let’s dive in and discover how identifiable enhances the way SwiftUI handles data in list views!

What is the Identifiable Protocol

In SwiftUI, the Identifiable protocol plays a crucial role in identifying and managing data in list views. It is a protocol provided by the Swift standard library that allows us to uniquely identify instances of a data type.

When a data type conforms to the Identifiable protocol, it must provide a property called id that uniquely identifies each instance. This id property can be of any type that conforms to the Hashable protocol, such as UUID, String, or Int.

You can find information about the Identifiable Procol conformance requirements in the documentation:

/// A class of types whose instances hold the value of an entity with stable
/// identity.

/// Conforming to the Identifiable Protocol
/// =======================================
///
/// `Identifiable` provides a default implementation for class types (using
/// `ObjectIdentifier`), which is only guaranteed to remain unique for the
/// lifetime of an object. If an object has a stronger notion of identity, it
/// may be appropriate to provide a custom implementation.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol Identifiable<ID> {

    /// A type representing the stable identity of the entity associated with
    /// an instance.
    associatedtype ID : Hashable

    /// The stable identity of the entity associated with this instance.
    var id: Self.ID { get }
}

By conforming to the Identifiable protocol, SwiftUI is able to efficiently track and update individual items in a list view. It enables SwiftUI to determine which items have been added, removed, or modified, and apply the necessary updates to the user interface.

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

Implementing Swift Identifiable in Your Data Model

To implement the identifiable protocol in your data model, follow these steps:

1. Declare conformance to the identifiable protocol in your data model struct or class.

struct Person: Identifiable {
    let id: String
    var name: String
}

Notice that I made the id a let constant and the name a var. The name property can be changed later by the user. But the id should always stay the same, which I enforce by making it a let. The id needs to stay the same otherwise SwiftUI will not be able to identify your data correctly and you may get funky animations and reordering of your UI.

2. Provide a unique identifier for each instance by adding an id property.

let person1 = Person(id: "1", name: "John")
let person2 = Person(id: "2", name: "Jane")

Make sure that the identifier property is unique for each instance. This uniqueness is crucial for Swift to correctly identify and update items in list views.

What Types Can You Use for ObjectIdentifiers?

The identifier property can be of any type that conforms to the Hashable protocol, such as UUID, String, Int, or even a custom type. Oftentimes, you will see UUIDs because they ensure that all IDs are unique. Every time I create a new UUID with the initializer UUID(), a new unique id will be generated:

struct Person: Identifiable {
    let uuid: UUID
    var name: String
    
    init(name: String, uuid: UUID = UUID() {
        self.name = name
        self.uuid = uuid
    }
}

If your data is coming from a server or another external source, ensure that the identifier is unique for each item. You can use a computed property to generate a unique identifier based on the data itself.

struct Person: Identifiable {
    let id: String
    let name: String
    // other properties...

    var identifier: String {
        // Generate a unique identifier based on the data
        return "\(name)-\(id)"
    }
}

How to Make Enum Types Conform to Swift Identifiable?

To implement Swift Identifiable in an enum, you need to provide a unique identifier for each case. You have a few options for achieving this:

  1. Use self as the identifier:
enum Animal: Identifiable {
    case dog
    case cat
    case bird
    
    var id: Self { return self }
}

2. Using Raw Values: If your enum has raw values of a type that conforms to Hashable, you can use the raw value as the identifier. For example, if you have an enum representing different categories of animals, you can use the animal’s name as the identifier:

enum Animal: String, Identifiable {
    case dog = "Dog"
    case cat = "Cat"
    case bird = "Bird"
    
    var id: String { return rawValue }
}

3. Using Associated Values: If your enum cases have associated values, you can use them as identifiers. For example, if you have an enum representing different types of tasks, you can use the task name as the identifier:

enum Task: Identifiable {
    case work(String)
    case study(String)
    
    var id: String {
        switch self {
        case .work(let name), .study(let name):
            return name
        }
    }
}

If you use another type that is identifiable as the associated value, you can return its id:

enum PeopleConnection: Identifiable {
    case friend(Person)
    case college(Person)
    
    var id: UUID {
        switch self {
        case .friend(let person), .college(let person):
            return person.id
        }
    }
}

How to Make a Type Identifiable by Itself?

You can use the type itself to identify it. The identity of the instance will change when any of its properties change.

This will increase the size of the label’s title and icon. If you want to make the image larger or smaller independently from the text, you can use the image scale modifier:

struct Pet: Identifiable {
   var name: String
   var animal: Animal
   var owner: Person
   
   var id: Self { return self }
}
Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

Examples of Identifiable in SwiftUI Views

In this section, I will walk you through a couple of examples to demonstrate how to use the Identifiable protocol in SwiftUI list views.

You can use Identifiable data with SwiftUI Lists. For example, we can show a list of people:

struct ContentView: View {
    @State private var people: [Person] = [
        Person(name: "John"),
        Person(name: "Jane"),
        Person(name: "Mike")
    ]
    
    var body: some View {
        NavigationStack {
            List(people) { person in
                Text(person.name)
            }
            .navigationTitle("Contacts")
        }
    }
}
example where identifiable is used with a swiftui list

Similarly, you can use identifiable data with ForEach. For example, if I want to add swipe on delete to the above example:

List {
    ForEach(people) { person in
        Text(person.name)
    }
    .onDelete(perform: { indexSet in
        people.remove(atOffsets: indexSet)
    })
}
swift identifiable data example with swiftui foreach

Additionally, you can use identifiable data with SwiftUI Pickers. For example, if you wanted to add a control view where the user selects its favorite animal:

private enum Animal: String, Identifiable, CaseIterable {
    case dog = "Dog"
    case cat = "Cat"
    case bird = "Bird"
    
    var id: Self { return self }
}

I made the Animal enum conform to CaseIterable Protocol to get an array of all cases, which is used to show all options in the picker:

struct ContentView: View {
    @State private var selectedAnimal: Animal = Animal.dog
    
    var body: some View {
        Picker("Favorite Animal", selection: $selectedAnimal) {
            ForEach(Animal.allCases) { animal in
                Text(animal.rawValue)
                    //.tag(animal)
            }
        }
        .pickerStyle(.segmented)
    }
}
swiftui example with identifiable enum type for a picker view

What Happens When SwiftUI Cannot Correctly Identify Your Data

I want to show you why having a stable identification of your views is important in SwiftUI. There is no better way to show you than by giving a bad example.

In the blow example, when the user presses on the plus button a new food item is attached to the data array. The new items always have the same name and icon. Because the list in this example uses the name property to identify the data, it finds multiple rows with the same name and identifies them the same. Thus, all new food items are treated as the same data, and the same view is used. These updating problems will increase if you move and delete data entries. You can avoid these issues by using stable identification.

struct Food {
    var name: String
    var icon: String
    var isFavorite: Bool
}

struct FoodListView: View {

    @State private var foods = Food.preview()

    var body: some View {
        NavigationView {
            List(foods, id: \.name) { food in
                HStack {
                    Text(food.icon)
                    Text(food.name)
                }
            }
            .toolbar {
                Button {
                    let newFood = Food(name: "new food",
                                       icon: "\(foods.count)",
                                       isFavorite: false)
                    foods.append(newFood)
                } label: {
                    Label("Add", systemImage: "plus")
                }
            }
        }
    }
}
list updating problems when not correctly making it identifiable

In this example multiple data items with the same identifier are used. Causing the list to not correctly show the data.

Another bad practice would be to use a computed property with a new UUID:

private enum Animal: String, Identifiable {
    case dog = "Dog"
    case cat = "Cat"
    case bird = "Bird"
    
    var id: UUID { return UUID() }
}

Every time the view updates where this data is used, SwiftUI will access the id property and decide if the data changed. Because the ID changed, a new view needs to be recreated. This can cause unnecessary redraws that can significantly decrease the performance of your app.

Make sure to not change the identifier once the data is instanciated.

Conclusion

In conclusion, identifying data in SwiftUI’s list views is crucial for efficient view rendering and seamless user experiences. By conforming to the Swift Identifiable protocol, you enable SwiftUI to uniquely identify each item in your data models. This identification allows SwiftUI to optimize view updates, animations, and transitions, resulting in smoother and more responsive interfaces.

Throughout this blog post, you have explored various scenarios where Swift Identifiable is used, such as in lists, pickers, tables, and more. By leveraging Identifiable, you can ensure that your SwiftUI apps perform optimally and provide a seamless user experience.

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form