Benutzerdefiniertes SwiftUI-Layout mit Simple Layout Engine
Die Mathematik, die für das benutzerdefinierte Layout von SwiftUI erforderlich ist, erinnert mich an die Tage vor AutoLayout und dem auf Beschränkungen basierenden System. Das Gute ist, dass Simple Layout Engine bereits ein nettes System bietet, um alle damit verbundenen mathematischen Berechnungen zu bewältigen. Um dies zu demonstrieren, würde ich die Teilmenge der Demo-App aus der WWDC-Sitzung zu diesem Thema erstellen: Erstellen Sie benutzerdefinierte Layouts mit SwiftUI .
Problem
Die Idee ist, eine Containeransicht ähnlich der zu haben, HStackin der jedes Kind die gleiche Breite hat, aber mit der Ausnahme, dass die Breite die des Maximums sein sollte, das ein Kind hat. So HStackwerden die Kinder standardmäßig platziert.
HStack {
WLText("hi")
WLText("!")
WLText("beautiful")
WLText("world")
}
Was wir eigentlich wollen, ist etwas, das so aussieht wie width = max(children.width) , was alle untergeordneten Elemente gleich der Breite des schönen Textes haben würde
BalancedHStack {
WLText("hi")
WLText("!")
WLText("beautiful")
WLText("world")
}
SwiftUIbietet eine Möglichkeit, sich in das Layoutsystem einzuklinken, um alle benutzerdefinierten Mathematikfunktionen bereitzustellen. Für unseren Fall können wir eine BalancedHStackprotokollkonforme erstellen Layout. Das LayoutProtokoll erfordert zwei Methoden:
sizeThatFits: UmCGSizedem System die Gesamtmenge des Behälters bereitzustellenplaceSubviews: Um die Positionen der Kinder innerhalb der vorgegebenen Grenzen zu aktualisieren- Einfache Layout-Engine
- Layout
- ViewThatFits
struct BalancedHStack: Layout {
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) -> CGSize {
fatalError()
// TODO
}
func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()) {
// TODO
}
}
struct BalancedHStack: Layout {
struct CacheData {
let childSize: CGSize
let distances: [CGFloat]
}
func makeCache(subviews: Subviews) -> CacheData {
let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
let width = subviewSizes.map { $0.width }.max() ?? 0
let height = subviewSizes.map { $0.height }.max() ?? 0
let distances: [CGFloat] = (0..<subviews.count).map { idx in
guard idx < subviews.count - 1 else { return 0 }
return subviews[idx].spacing.distance(to: subviews[idx + 1].spacing, along: .horizontal)
}
return CacheData(
childSize: CGSize(width: width, height: height),
distances: distances
)
}
// ...
}
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout CacheData) -> CGSize {
let totalDistance = cache.distances.reduce(0, +)
return CGSize(
width: cache.childSize.width * CGFloat(subviews.count) + totalDistance,
height: cache.childSize.height
)
}
func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout CacheData) {
let layout = SLELayout(parentFrame: bounds, direction: .row, alignment: .center)
do {
var items: [SLEItem] = []
for idx in 0..<subviews.count {
items.append(try layout.add(item: .size(cache.childSize)))
try layout.add(item: .width(cache.distances[idx]))
}
for (idx, subview) in subviews.enumerated() {
subview.place(
at: try items[idx].frame().origin,
proposal: ProposedViewSize(cache.childSize)
)
}
}
catch { print("Unable to layout \(error)") }
}
extension SLEDirection {
var axis: Axis {
switch self {
case .row: return .horizontal
case .column: return .vertical
}
}
}
struct BalancedStack: Layout {
let direction: SLEDirection
init(_ direction: SLEDirection) {
self.direction = direction
}
struct CacheData {
let childSize: CGSize
let distances: [CGFloat]
}
func makeCache(subviews: Subviews) -> CacheData {
let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
let width = subviewSizes.map { $0.width }.max() ?? 0
let height = subviewSizes.map { $0.height }.max() ?? 0
let distances: [CGFloat] = (0..<subviews.count).map { idx in
guard idx < subviews.count - 1 else { return 0 }
return subviews[idx].spacing.distance(to: subviews[idx + 1].spacing, along: direction.axis)
}
return CacheData(
childSize: CGSize(width: width, height: height),
distances: distances
)
}
func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout CacheData) -> CGSize {
let totalDistance = cache.distances.reduce(0, +)
let containerWidth: CGFloat
let containerHeight: CGFloat
switch direction {
case .row:
containerWidth = cache.childSize.width * CGFloat(subviews.count) + totalDistance
containerHeight = cache.childSize.height
case .column:
containerWidth = cache.childSize.width
containerHeight = cache.childSize.height * CGFloat(subviews.count) + totalDistance
}
return CGSize(width: containerWidth, height: containerHeight)
}
func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout CacheData) {
let layout = SLELayout(parentFrame: bounds, direction: direction, alignment: .center)
do {
var items: [SLEItem] = []
for idx in 0..<subviews.count {
items.append(try layout.add(item: .size(cache.childSize)))
try layout.add(item: .width(cache.distances[idx]))
}
for (idx, subview) in subviews.enumerated() {
subview.place(
at: try items[idx].frame().origin,
proposal: ProposedViewSize(cache.childSize)
)
}
}
catch { print("Unable to layout \(error)") }
}
}
struct TextList: View {
var body: some View {
WLText("hi")
WLText("!")
WLText("beautiful")
WLText("world")
}
}
ViewThatFits {
BalancedStack(.row) {
TextList()
}
BalancedStack(.column) {
TextList()
}
}

![Was ist überhaupt eine verknüpfte Liste? [Teil 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































