Scroll into view when a Timeline Item is selected if it isn't completely visible
This commit is contained in:
parent
f9cd15970f
commit
71a6f03fd6
|
@ -11,9 +11,10 @@ import SwiftUI
|
||||||
struct TimelineView: View {
|
struct TimelineView: View {
|
||||||
|
|
||||||
@EnvironmentObject private var timelineModel: TimelineModel
|
@EnvironmentObject private var timelineModel: TimelineModel
|
||||||
|
@State private var timelineItemFrames = [String: CGRect]()
|
||||||
|
|
||||||
@ViewBuilder var body: some View {
|
@ViewBuilder var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { geometryReaderProxy in
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -35,22 +36,60 @@ struct TimelineView: View {
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||||
}
|
}
|
||||||
List(timelineModel.timelineItems, selection: $timelineModel.selectedArticleIDs) { timelineItem in
|
ScrollViewReader { scrollViewProxy in
|
||||||
let selected = timelineModel.selectedArticleIDs.contains(timelineItem.article.articleID)
|
List(timelineModel.timelineItems, selection: $timelineModel.selectedArticleIDs) { timelineItem in
|
||||||
TimelineItemView(selected: selected, width: proxy.size.width, timelineItem: timelineItem)
|
let selected = timelineModel.selectedArticleIDs.contains(timelineItem.article.articleID)
|
||||||
|
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||||
|
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||||
|
}
|
||||||
|
.onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in
|
||||||
|
for pref in preferences {
|
||||||
|
timelineItemFrames[pref.articleID] = pref.frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: timelineModel.selectedArticleIDs) { selectedArticleIDs in
|
||||||
|
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
||||||
|
for articleID in selectedArticleIDs {
|
||||||
|
if let itemFrame = timelineItemFrames[articleID] {
|
||||||
|
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 35 {
|
||||||
|
withAnimation {
|
||||||
|
scrollViewProxy.scrollTo(articleID, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
|
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
|
||||||
#else
|
#else
|
||||||
List(timelineModel.timelineItems) { timelineItem in
|
ScrollViewReader { scrollViewProxy in
|
||||||
ZStack {
|
List(timelineModel.timelineItems) { timelineItem in
|
||||||
let selected = timelineModel.selectedArticleID == timelineItem.article.articleID
|
ZStack {
|
||||||
TimelineItemView(selected: selected, width: proxy.size.width, timelineItem: timelineItem)
|
let selected = timelineModel.selectedArticleID == timelineItem.article.articleID
|
||||||
NavigationLink(destination: ArticleContainerView(),
|
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||||
tag: timelineItem.article.articleID,
|
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||||
selection: $timelineModel.selectedArticleID) {
|
NavigationLink(destination: ArticleContainerView(),
|
||||||
EmptyView()
|
tag: timelineItem.article.articleID,
|
||||||
}.buttonStyle(PlainButtonStyle())
|
selection: $timelineModel.selectedArticleID) {
|
||||||
|
EmptyView()
|
||||||
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in
|
||||||
|
for pref in preferences {
|
||||||
|
timelineItemFrames[pref.articleID] = pref.frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: timelineModel.selectedArticleID) { selectedArticleID in
|
||||||
|
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
||||||
|
if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] {
|
||||||
|
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 {
|
||||||
|
withAnimation {
|
||||||
|
scrollViewProxy.scrollTo(articleID, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline)
|
.navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline)
|
||||||
|
@ -59,3 +98,31 @@ struct TimelineView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TimelineItemFramePreferenceKey: PreferenceKey {
|
||||||
|
typealias Value = [TimelineItemFramePreference]
|
||||||
|
|
||||||
|
static var defaultValue: [TimelineItemFramePreference] = []
|
||||||
|
|
||||||
|
static func reduce(value: inout [TimelineItemFramePreference], nextValue: () -> [TimelineItemFramePreference]) {
|
||||||
|
value.append(contentsOf: nextValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimelineItemFramePreference: Equatable {
|
||||||
|
let articleID: String
|
||||||
|
let frame: CGRect
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimelineItemFramePreferenceView: View {
|
||||||
|
let timelineItem: TimelineItem
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.clear)
|
||||||
|
.preference(key: TimelineItemFramePreferenceKey.self,
|
||||||
|
value: [TimelineItemFramePreference(articleID: timelineItem.article.articleID, frame: proxy.frame(in: .global))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue