Tyten

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))
    }
}
Tagged with: