magnuskahr

writing code

Nested reusability in SwiftUI

Recently, I have had to create many different forms in SwiftUI. You see, these "forms" had to be reusable, as they would be the root of both a create and an edit screen, where the main difference was some buttons at the bottom. In the following, I am going to share how I structured such a task.

Nested structs

Say we have a simple form to enter a title:

struct TitleForm: View {
	@Binding var title: String
	var body: some View {
		Form {
			TextField("Title", text: $title)
		}
	}
}

We would then need to reuse this form to both create and edit titles, each case with its bottom buttons. What I find here, is that nesting structs have a great use, so we end up with the following api: TitleForm.Create() and TitleForm.Edit(initialTitle: "some title"). To do so, we will have two nested structs:

extension TitleForm {
	struct Create: View {
	
		@State private var title = ""
		let onCreate: (String) -> Void
		
		var body: some View {
			TitleForm(title: $title)
				.safeAreaInset(edge: .bottom) {
					Button("Create") { /**/ }
						.buttonStyle(.borderedProminent)
				}	
		}
	}
}

Now we can use the TitleForm to create titles:

TitleForm.Create { created in
	print("Title created: ", created)
}

Likewise, we can have another nested struct for editing:

extension TitleForm {
	struct Edit: View {
	
		@State private var title: String
		
		private let onEdit: (String) -> Void
		private let onDelete: () -> Void
		
		init(initialTitle title: String, onEdit: @escaping (String) -> Void, onDelete: @escaping () -> Void) {
			self._title = State(initialValue: title)
			self.onEdit = onEdit
			self.onDelete = onDelete
		}
		
		var body: some View {
			TitleForm(title: $title)
				.safeAreaInset(edge: .bottom) {
					HStack {
						Button("Delete") { /**/ }
							.buttonStyle(.bordered)
						Button("Save") { /**/ }
							.buttonStyle(.borderedProminent)
					}
				}
		}
	}
}

And use the TitleForm to edit titles:

TitleForm.Edit(
	initialTitle: "Old title",
	onEdit: { print("New title: ", $0) },
	onDelete: { print("Delete the title") }
)

Conclusion

Nested structs can be a great way to keep a good structure, and allows us to reuse our views to build higher levels screens that based on the same view act differently. We have in this post seen how the same form can adapt to both a creation and edit situation. While this is not limited to nested structs, doing so gives a clear namespace and makes the code easier to reason about.