A common motivation for defining a custom element is to create an API boundary; the custom implementation can contain its own initializers, properties, etc.
When it comes to actually displaying content, however, many custom elements simply compose other existing elements together (they don't do custom layout or provide a custom view backing).
ProxyElement
is a tool to make this common case easier.
public protocol ProxyElement: Element {
var elementRepresentation: Element { get }
}
/// `ProxyElement` provides default implementations of the `Element` API that delegate to the element returned by `elementRepresentation`.
extension ProxyElement {
public var content: ElementContent { get }
public func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription?
}
For example, let's define a custom element that displays a title and subtitle:
struct TitleSubtitleElement: ProxyElement {
var title: String
var subtitle: String
var elementRepresentation: Element {
Column(
alignment: .leading,
minimumSpacing: 8.0
) {
Label(text: title) { label in
label.font = .boldSystemFont(ofSize: 18.0)
}
Label(text: title) { label in
label.font = .systemFont(ofSize: 14.0)
label.color = .darkGray
}
}
}
}
For more information, please see Element
reference.
public protocol Element {
var content: ElementContent { get }
func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription?
}
To implement the Element
protocol, your custom type must implement two methods:
ElementContent
represents (surprise!) the content of an element. Elements generally fall into one of two types: containers and leaves.
- Containers, or elements that have children.
- Leaves: elements that have no children, but often have some intrinsic size (a label is a good example of this).
If a ViewDescription
is returned, the element will be view-backed.
You may want to define a custom element so that you can reuse it throughout your codebase (great!). In other cases, however, you may simply want to generate an element for display.
For example, let's say we want to show some centered text over an image. This is only used in one place:
final class ImageViewController {
private let blueprintView = BlueprintView()
var image: UIImage {
didSet { updateDisplay() }
}
var text: String {
didSet { updateDisplay() }
}
private func updateDisplay() {
let newElement = // We need an element!
blueprintView.element = newElement
}
}
You might simply define a function to turn input (in this case an image and some text) into an element:
private func makeElement(image: UIImage, text: String) -> Element {
Overlay {
Image(image: image)
Label(text: text)
.centered()
}
}
Your updateDisplay()
method can then call this method as needed:
final class ImageViewController {
// ...
private func updateDisplay() {
blueprintView.element = makeElement(image: image, text: text)
}
}