Skip to content

Instantly share code, notes, and snippets.

@satishVekariya
Created May 12, 2023 18:46
Show Gist options
  • Save satishVekariya/bfdb56192f6d61a7b43636b17eec06a9 to your computer and use it in GitHub Desktop.
Save satishVekariya/bfdb56192f6d61a7b43636b17eec06a9 to your computer and use it in GitHub Desktop.
//
// LastAtBottomVStack.swift
//
//
// Created by Satish Vekariya on 21/04/2023.
//
import SwiftUI
// MARK: - LastAtBottomVStack
/// A view that arranges its subviews in a vertical line and place last view at bottom or at last position based on given `viewPortSize`.
///
/// Recommendation: Embed this layout inside `GeometryReader` & `ScrollView` like:
///
/// ```
/// GeometryReader { geo in
/// ScrollView {
/// LastAtBottomVStack(viewPortSize: geo.size) {
/// Rectangle().fill(.red)
/// .frame(height: 150)
/// Rectangle().fill(.blue)
/// .frame(height: 130)
/// Rectangle().fill(.gray) /// Will stay at bottom or last
/// .frame(height: 200)
/// }
/// }
/// }
/// ```
///
public struct LastAtBottomVStack: Layout {
public let viewPortSize: CGSize
public init(viewPortSize: CGSize) {
self.viewPortSize = viewPortSize
}
public func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
)
-> CGSize {
.init(
/// View's width
width: viewPortSize.width,
/// Calculate total height required for subviews and pick max value from total heigh and view port height
height: max(viewPortSize.height, subviews.reduce(0) { $0 + $1.dimensions(in: proposal).height })
)
}
public func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
var occupiedHeight = bounds.origin.y // Set initial value
for view in subviews {
/// This view height
let thisViewHeight = view.dimensions(in: proposal).height
/// Check, can place view at bottom within bound
let canPlaceLastViewAtBottom = thisViewHeight + occupiedHeight >= bounds.height
let yPosition: CGFloat
if subviews.count > 1 && view == subviews.last && !canPlaceLastViewAtBottom {
/// Set bottom position within bounds
yPosition = bounds.height - thisViewHeight
} else {
/// Set last position irrespective to bounds
yPosition = occupiedHeight
}
/// Set view's position
view.place(at: .init(x: bounds.midX, y: yPosition), anchor: .top, proposal: proposal)
/// Increment `occupiedHeight` value
occupiedHeight += thisViewHeight
}
}
}
// MARK: - LastAtBottomVStack_Previews
struct LastAtBottomVStack_Previews: PreviewProvider {
static var previews: some View {
GeometryReader { geo in
ScrollView {
LastAtBottomVStack(viewPortSize: geo.size) {
Rectangle().fill(.red)
.frame(height: 150)
Rectangle().fill(.blue)
.frame(height: 130)
Rectangle().fill(.gray) /// Will stay at bottom or last
.frame(height: 200)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment