Files
letro-ios/ElementX/Sources/Screens/RoomScreen/View/TimelineScrollView.swift
Doug b96205cac1 Use a VStack on the timeline (#332)
* Use a VStack for the timeline.

Replace edge publishers with a binding.

* Allow both top and bottom edges to be detected.

* Fix scrolling with frame changes.
2022-11-22 13:28:35 +00:00

77 lines
2.8 KiB
Swift

//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
struct VisibleEdgesKey: PreferenceKey {
static var defaultValue: [VerticalEdge] = []
static func reduce(value: inout [VerticalEdge], nextValue: () -> [VerticalEdge]) {
value = nextValue()
}
}
/// A SwiftUI scroll view with the following customisations for a room timeline
/// - The content is laid out starting at the bottom.
/// - Top and bottom edge visibility detection for triggering other behaviours.
struct TimelineScrollView<Content: View>: View {
@Binding var visibleEdges: [VerticalEdge]
@ViewBuilder var content: () -> Content
/// A small threshold added to the edge detection to allow a bit of leniency.
private let edgeDetectionThreshold: CGFloat = 15
var body: some View {
GeometryReader { scrollViewGeometry in
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Spacer()
content()
}
.frame(minHeight: scrollViewGeometry.size.height)
.background {
GeometryReader { contentGeometry in
Color.clear
.preference(key: VisibleEdgesKey.self,
value: visibleEdges(of: contentGeometry, in: scrollViewGeometry))
}
.onPreferenceChange(VisibleEdgesKey.self) {
visibleEdges = $0
}
}
}
}
}
func visibleEdges(of contentGeometry: GeometryProxy, in scrollViewGeometry: GeometryProxy) -> [VerticalEdge] {
let frame = contentGeometry.frame(in: .global)
let isTopVisible = scrollViewGeometry.frame(in: .global).contains(CGPoint(x: frame.midX, y: frame.minY + edgeDetectionThreshold))
let isBottomVisible = scrollViewGeometry.frame(in: .global).contains(CGPoint(x: frame.midX, y: frame.maxY - edgeDetectionThreshold))
switch (isTopVisible, isBottomVisible) {
case (false, false):
return []
case (true, false):
return [.top]
case (false, true):
return [.bottom]
case (true, true):
return [.top, .bottom]
}
}
}