📝
【SwiftUI】ヘッダーの左右中央にビューを配置する(Layoutプロトコル)
struct ContentView: View {
var body: some View {
Header {
Text("CenterContent")
} leftContent: {
Text("LeftContent")
} rightContent: {
Text("RightContent")
}
.border(.pink)
.padding(.horizontal)
}
}
struct Header<C1: View, C2: View, C3: View>: View {
var idealSpacing: CGFloat = 100
@ViewBuilder let centerContent: C1
@ViewBuilder let leftContent: C2
@ViewBuilder let rightContent: C3
var body: some View {
HeaderLayout(idealSpacing: idealSpacing) {
leftContent; centerContent; rightContent
}
}
}
/// subviews[0]: leftContent, subviews[1]: centerContent, subviews[2]: rightContent
///
struct HeaderLayout: Layout {
var idealSpacing: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
guard subviews.count == 3 else { fatalError() }
if let width = proposal.width {
let proposal = ProposedViewSize(width: proposal.width, height: nil)
let subviewSizes = subviews.map { $0.sizeThatFits(proposal) }
let additionalSpacing = max(subviewSizes[0].width, subviewSizes[2].width) - min(subviewSizes[0].width, subviewSizes[2].width)
let minWidth = subviewSizes.reduce(0) { $0 + $1.width } + additionalSpacing + 20
let width = max(width, minWidth)
let height = subviewSizes.max { $0.height < $1.height }!.height
return .init(width: width, height: height)
} else {
let leftContentSize = subviews[0].sizeThatFits(.unspecified)
let centerContentSize = subviews[1].sizeThatFits(.unspecified)
let rightContentSize = subviews[2].sizeThatFits(.unspecified)
let additionalSpacing = max(leftContentSize.width, rightContentSize.width) - min(leftContentSize.width, rightContentSize.width)
let spacing = idealSpacing * 2 + additionalSpacing
let width = leftContentSize.width + centerContentSize.width + rightContentSize.width + spacing
let height = max(leftContentSize.height, centerContentSize.height, rightContentSize.height)
return .init(width: width, height: height)
}
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
guard subviews.count == 3 else { fatalError() }
let proposal = ProposedViewSize(width: proposal.width, height: nil)
let leftContentPosition = CGPoint(x: bounds.minX, y: bounds.midY)
subviews[0].place(at: leftContentPosition, anchor: .leading, proposal: proposal)
let centerContentPosition = CGPoint(x: bounds.midX, y: bounds.midY)
subviews[1].place(at: centerContentPosition, anchor: .center, proposal: proposal)
let rightContentPosition = CGPoint(x: bounds.maxX, y: bounds.midY)
subviews[2].place(at: rightContentPosition, anchor: .trailing, proposal: proposal)
}
}
Discussion