Enabling reuse with protocols
Given that one of our primary goals with a design system is to create reusable components, we need to make sure that the following criteria are met:
- The components adhere to a rigid information hierarchy.
- The components are do not have intimate, tightly coupled knowledge of the data they display/interact with.
To that end, since we can't know anything about the actual data, what we can do is set up a protocol for our data / data models / anything.
Displayable
The protocol itself is pretty simple and establishes a clear hierarchy:
public protocol Displayable {
// Text levels
var primaryText: String? { get }
var secondaryText: String? { get }
var tertiaryText: String? { get }
var quarternaryText: String? { get }
// Image properties
var image: UIImage? { get }
// Does the image have a mask?
var imageMask: ImageMask { get }
}
Strictness
This protocol defines a very clear information hierarchy for a given element. There are:
- Four levels of text.
- An image.
- An image mask (maybe you'll need rounded corners or a circle?).
Every component will take in a Displayable
and use it to populate its view. Don't want to make something conform to Displayable
? Well, that won't be usable by a component. Conversely, you could make anything Displayable
and throw it into a component.
That does not guarantee every piece provided will be displayed. Maybe a view header wants to only show the primary and secondary text, but not an image. That would just be ignored.
Quick Example
Let's say we have a component for items in a List
called Row
.
public struct Row: View {
public var displayable: Displayable
public init(displayable: Displayable) {
self.displayable = displayable
}
public var body: some View {
HStack {
// Call a convenience method that returns true if the value is not nil,
// or if a non-nil String, isn't empty.
if hasContent(displayable.image) {
Image(uiImage: displayable.image ?? UIImage())
.resizable()
.scaledToFill()
.frame(width: Sizes.rowImageWidth,
height: Sizes.rowImageHeight,
alignment: .center)
.clipShape(displayable.imageMask)
}
RowText(displayable: displayable)
}
.padding(.all, Spacing.double)
.background(Color(.backgroundPrimary))
}
}