Scroll into view when a Timeline Item is selected if it isn't completely visible

This commit is contained in:
Maurice Parker 2020-07-22 20:40:04 -05:00
parent f9cd15970f
commit 71a6f03fd6
1 changed files with 80 additions and 13 deletions

View File

@ -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,17 +36,39 @@ 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")
} }
ScrollViewReader { scrollViewProxy in
List(timelineModel.timelineItems, selection: $timelineModel.selectedArticleIDs) { timelineItem in List(timelineModel.timelineItems, selection: $timelineModel.selectedArticleIDs) { timelineItem in
let selected = timelineModel.selectedArticleIDs.contains(timelineItem.article.articleID) let selected = timelineModel.selectedArticleIDs.contains(timelineItem.article.articleID)
TimelineItemView(selected: selected, width: proxy.size.width, timelineItem: timelineItem) 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
ScrollViewReader { scrollViewProxy in
List(timelineModel.timelineItems) { timelineItem in List(timelineModel.timelineItems) { timelineItem in
ZStack { ZStack {
let selected = timelineModel.selectedArticleID == timelineItem.article.articleID let selected = timelineModel.selectedArticleID == timelineItem.article.articleID
TimelineItemView(selected: selected, width: proxy.size.width, timelineItem: timelineItem) TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
NavigationLink(destination: ArticleContainerView(), NavigationLink(destination: ArticleContainerView(),
tag: timelineItem.article.articleID, tag: timelineItem.article.articleID,
selection: $timelineModel.selectedArticleID) { selection: $timelineModel.selectedArticleID) {
@ -53,9 +76,53 @@ struct TimelineView: View {
}.buttonStyle(PlainButtonStyle()) }.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)
#endif #endif
} }
} }
} }
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))])
}
}
}