The @Fragment property wrapper allows a SwiftUI view to specify exactly the data it needs to be able to render and guarantee that data will be fetched by a @Query higher in the view tree.

Using fragments isolates views from needing to know details about what data other views depend on. They allow views to be reused in different parts of the app with zero risk of forgetting to fetch necessary data. When using fragments, the Swift and Relay compilers work together to ensure that queries include all data that is needed by all child views and that the data is being passed between views correctly.

Example

import SwiftUI
import RelaySwiftUI

private let itemFragment = graphql("""
fragment ToDoItem_item on Item {
  text
	complete
}
""")

struct ToDoItem: View {
  @Fragment<ToDoItem_item> var item

	var body: some View {
		Group {
			if let item = item {
				HStack {
					Image(systemName: item.complete ? "checkmark.square" : "square")
					Text("\\(item.text)")
				}
			}
		}
	}
}

Passing data into fragment views

Fragments don't fetch any data themselves. Instead, they expect it to be fetched by a view higher up in the tree using @Query. To ensure the data the fragment needs is fetched by the query, you must spread the fragment into the GraphQL query. Once you've done so, you should be able to pass the data along to a child view that's using @Fragment:

private let query = graphql("""
query ToDoListQuery {
  list(id: "abc") {
		items {
			...ToDoItem_item
		}
	}
}
""")

struct ToDoList: View {
	@Query<ToDoListQuery> var query

	var body: some View {
		switch query.get() {
			case .loading:
				Text("Loading...")
			case .failure(let error):
				Text("Error: \\(error.localizedDescription)")
			case .success(let data):
				List(data?.list?.items ?? [], id: \\.id) { toDoItem in
					ToDoItem(item: toDoItem.asFragment())
				}
		}
	}
}

To pass the fragment along to the child view, call asFragment() on the value where the fragment was spread. This creates a value of the Fragment type that matches the property declared on the child view using @Fragment.

Fragments can also be spread into other fragments (not just queries), allowing for arbitrarily deep view trees where each view requests exactly the data it needs.

Masking

While spreading a fragment into a GraphQL query includes the necessary fields in the query that is sent to the server, it does not make those fields available to the view. In the examples above, for instance, the ToDoList view cannot read the text or checked properties of any to-do items, even though they were fetched as part of the query. Instead, the type generated for to-do items for the ToDoListQuery conforms to the ToDoItem_item_Key protocol and includes a "pointer" to the data for that to-do item in the Relay store. The @Fragment property wrapper will use that pointer to look up the data that the ToDoItem view needs.

It may seem unnecessary to go to all this trouble to make views hide data from each other, but it has significant benefits as you're working on your app. It means that each view in your application always specifies exactly what data it depends on in the same file as the code that uses that data. If you need to change which fields your view needs, you can just update your GraphQL fragment and you're good to go. The query that fetches the data doesn't need any changes because it doesn't even know what your component needs in the first place.

Parameters

Property value

The @Fragment property will be a read-only optional value with the fields the fragment requests.