Skip to content

Instantly share code, notes, and snippets.

@satishVekariya
Last active May 31, 2024 22:08
Show Gist options
  • Save satishVekariya/c52477b6acafdf200606335e39a37382 to your computer and use it in GitHub Desktop.
Save satishVekariya/c52477b6acafdf200606335e39a37382 to your computer and use it in GitHub Desktop.
These modifiers are designed to observe changes in the view's bounds and provide a stretchy background effect for a list.
import SwiftUI
/// These modifiers are designed to observe changes in the view's bounds and provide a stretchy background effect for a list.
///
/// Source: https://gist.github.com/satishVekariya/c52477b6acafdf200606335e39a37382
public extension View {
/// List/View bound change observer
///
/// Mostly you need to use this on List cell
///
/// ```
/// @State private var offset: CGPoint?
///
/// List {
/// Text("hi")
/// .onBoundChange() {
/// offset = $0?.origin
/// }
/// }
/// .stretchyBackground(offsetY: offset?.y)
/// ```
/// Reference: https://stackoverflow.com/questions/56726369/getting-content-offset-of-list-in-swiftui
///
/// - Parameters:
/// - id: A unique id
/// - onChange: Closure to that receives new frame value
/// - Returns: some View
func onBoundChange(
id: String = "bound",
onChange: @escaping (CGRect?) -> Void
) -> some View {
self.background(
GeometryReader { geo in
Color.clear
.preference(key: BoundPreferenceKey.self, value: [ViewFrame(id: id, frame: geo.frame(in: .named("name")))])
}
)
.onPreferenceChange(BoundPreferenceKey.self) { frames in
onChange(frames.first(where: { $0.id == id })?.frame)
}
}
/// Use this modifier on List view to set fixed and stretchy background
/// - Parameters:
/// - alignment: Alignment of the stretchy background
/// - fixedColor: Background color to fill entire screen. Default is white
/// - stretchyColor: Background color to display on top of fixed color with dynamic height
/// - offsetY: Scroll offset to adjust stretchy of stretchyColor. Mostly given by `onBoundChange(_:)` modifier
/// - Returns: some View
func stretchyBackground(
alignment: Alignment = .top,
fixedColor: Color = .white,
stretchyColor: Color = .blue,
offsetY: CGFloat?
) -> some View {
self.background(
ZStack(alignment: alignment) {
fixedColor
if let offsetY = offsetY, offsetY > 0 {
stretchyColor.frame(height: offsetY)
}
}
.ignoresSafeArea()
.coordinateSpace(name: "name")
)
}
}
fileprivate struct ViewFrame: Equatable {
let id: String
let frame: CGRect
static func == (lhs: ViewFrame, rhs: ViewFrame) -> Bool {
lhs.id == rhs.id && lhs.frame == rhs.frame
}
}
fileprivate struct BoundPreferenceKey: PreferenceKey {
typealias Value = [ViewFrame] // The list of view frame changes in a View tree.
static var defaultValue: [ViewFrame] = []
/// When traversing the view tree, Swift UI will use this function to collect all view frame changes.
static func reduce(value: inout [ViewFrame], nextValue: () -> [ViewFrame]) {
value.append(contentsOf: nextValue())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment