Комментарии 6
Идея неплохая, но почему бы не использовать LazyVStack
с pinnedViews
? или религия таргет не позволяет? :)
вроде такой подход не сработает так как pinnedViews норм для хэдеров и футеров, а тут хотели любой айтем пинить
Вызов был принят ) Собственно наскоро запилил примерчик.
Выглядит вот так:
Код не претендует на перфенционизм автора, но реализован через хедер секции. А если еще и с напильником, чтобы краюшки скруглить где надо...
enum Constants {
static let scrollViewCoordinateSpace = "ScrollView"
static let itemHeight: CGFloat = 40
}
struct ContentView: View {
enum ScrollDirection {
case idle
case up
case down
}
@State private var selectedItemIndex: Int? = .none
@State private var selectedItemOffset = CGFloat.zero
@State private var scrollDirection: ScrollDirection = .idle
var offsetFactor: CGFloat {
switch scrollDirection {
case .idle, .down: 0
case .up: Constants.itemHeight
}
}
var body: some View {
NavigationStack {
ScrollView {
LazyVStack(pinnedViews: [.sectionHeaders]) {
Section {
ForEach(1...100, id: \.self) { count in
itemView(for: count)
.background(GeometryReader { proxy in
if selectedItemIndex == count {
Color.clear.preference(
key: ViewOffsetKey.self,
value: proxy.frame(in: .named(Constants.scrollViewCoordinateSpace)).minY
)
}
})
}
} header: {
selectedPinnedView
}
}
.padding()
.onPreferenceChange(ViewOffsetKey.self) {
if selectedItemOffset < $0 {
scrollDirection = .up
} else {
scrollDirection = .down
}
if selectedItemOffset <= -UIScreen.main.bounds.height
&& $0 < selectedItemOffset { return }
self.selectedItemOffset = $0
}
}
.coordinateSpace(name: Constants.scrollViewCoordinateSpace)
.navigationTitle("SelectedPinnedView")
.navigationBarTitleDisplayMode(.inline)
}
}
func itemView(for index: Int) -> some View {
Button(action: {
selectedItemIndex = index
}) {
Text("Item \(index)")
.foregroundStyle(.white)
.frame(height: Constants.itemHeight)
.frame(maxWidth: .infinity)
.background(selectedItemIndex == index ? .green : .gray)
.clipShape(RoundedRectangle(cornerRadius: 8))
.contentShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
}
@ViewBuilder
var selectedPinnedView: some View {
if let selectedItemIndex, selectedItemOffset < 0 + offsetFactor {
itemView(for: selectedItemIndex)
}
}
}
ЗЫ.: Отредактировал onPreferenceChange
, заметил, что LazyVStack
убирает View
когда она выходит за 1 высоту экрана, отчего offset выставлялся в 0. Но теперь все гладко и шелковисто.
Такая идея была, но не придумала, как ее использовать для того, чтобы прилеплять любой элемент (отличный от хедера или футера). Сейчас вижу ваш ответ с реализацией такого способа решения - классно, оказалось очень познавательно ? Спасибо!
Крутенько, только недавно задавался вопросом, сделать на подобии
ScrollView с прилипающим выделенным элементом на SwiftUI