Last active
April 7, 2025 13:29
-
-
Save TAATHub/a4eda1ab82725802d7265b99aaae894d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // See also: Pro SwiftUI, Chapter 4 Custom Layouts | |
| import SwiftUI | |
| struct RelativeHStack: Layout { | |
| enum Alignment { | |
| case top, center, bottom | |
| } | |
| var alignment: Alignment = .center | |
| var spacing = 0.0 | |
| func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { | |
| let width = proposal.replacingUnspecifiedDimensions().width | |
| let viewFrames = frames(for: subviews, in: width) | |
| let height = viewFrames.max { $0.maxY < $1.maxY } ?? .zero | |
| return CGSize(width: width, height: height.maxY) | |
| } | |
| func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { | |
| let viewFrames = frames(for: subviews, in: bounds.width) | |
| for index in subviews.indices { | |
| let frame = viewFrames[index] | |
| let y = switch alignment { | |
| case .top: bounds.minY | |
| case .center: bounds.midY | |
| case .bottom: bounds.maxY | |
| } | |
| let position = CGPoint(x: bounds.minX + frame.minX, y: y) | |
| let anchor: UnitPoint = switch alignment { | |
| case .top: .topLeading | |
| case .center: .leading | |
| case .bottom: .bottomLeading | |
| } | |
| subviews[index].place(at: position, anchor: anchor, proposal: .init(frame.size)) | |
| } | |
| } | |
| private func frames(for subviews: Subviews, in totalWidth: Double) -> [CGRect] { | |
| let totalSpacing = spacing * Double(subviews.count - 1) | |
| let availableWidth = totalWidth - totalSpacing | |
| let totalPriorities = subviews.reduce(0) { $0 + $1.priority } | |
| var viewFrames = [CGRect]() | |
| var x = 0.0 | |
| for subview in subviews { | |
| let subviewWidth = availableWidth * subview.priority / totalPriorities | |
| let proposal = ProposedViewSize(width: subviewWidth, height: nil) | |
| let size = subview.sizeThatFits(proposal) | |
| let frame = CGRect(x: x, y: 0, width: size.width, height: size.height) | |
| viewFrames.append(frame) | |
| x += size.width + spacing | |
| } | |
| return viewFrames | |
| } | |
| } | |
| struct ContentView: View { | |
| var body: some View { | |
| NavigationStack { | |
| VStack(spacing: 40) { | |
| VStack(alignment: .leading, spacing: 4) { | |
| Text("alignment: .top") | |
| relativeHStackView(alignment: .top) | |
| .border(.black) | |
| } | |
| VStack(alignment: .leading, spacing: 4) { | |
| Text("alignment: .center") | |
| relativeHStackView(alignment: .center) | |
| .border(.black) | |
| } | |
| VStack(alignment: .leading, spacing: 4) { | |
| Text("alignment: .bottom") | |
| relativeHStackView(alignment: .bottom) | |
| .border(.black) | |
| } | |
| } | |
| .padding() | |
| .navigationTitle("Relative Width Layout") | |
| } | |
| } | |
| private func relativeHStackView(alignment: RelativeHStack.Alignment) -> some View { | |
| RelativeHStack(alignment: alignment, spacing: 40) { | |
| Group { | |
| Text("1") | |
| .frame(maxWidth: .infinity) | |
| .frame(height: 40) | |
| .background(.red) | |
| .layoutPriority(1) | |
| Text("2") | |
| .frame(maxWidth: .infinity) | |
| .frame(height: 60) | |
| .background(.green) | |
| .layoutPriority(2) | |
| Text("3") | |
| .frame(maxWidth: .infinity) | |
| .frame(height: 80) | |
| .background(.blue) | |
| .layoutPriority(3) | |
| } | |
| .font(.title3) | |
| .foregroundStyle(.white) | |
| } | |
| } | |
| } | |
| #Preview { | |
| ContentView() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment