結論
import SwiftUI
struct ContentView: View {
@State private var scrollTrigger = ChangeTransmitter()
var body: some View {
VStack {
MyScrollView(scrollTrigger: $scrollTrigger)
Button("scroll to bottom") {
scrollTrigger.trigger()
}
}
}
}
struct MyScrollView: View {
@Binding var scrollTrigger: ChangeTransmitter
private let scrollTarget = "bottomID"
var body: some View {
ScrollViewReader { proxy in
ScrollView {
ForEach(0..<100) { index in
Text("\(index)")
}
Color.clear.frame(height: 1)
.id(scrollTarget)
}
.onChange(of: scrollTrigger) { _, _ in
withAnimation {
proxy.scrollTo(scrollTarget)
}
}
}
}
}
struct ChangeTransmitter: Equatable {
private var changeCount = 0
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.changeCount == rhs.changeCount
}
mutating func trigger() {
self.changeCount += 1
}
}
#Preview {
ContentView()
}
経緯
このように書いても…
import SwiftUI
struct ContentView: View {
@State private var scrollTarget: Int?
var body: some View {
VStack {
MyScrollView(scrollTarget: $scrollTarget)
Button("scroll") {
scrollTarget = 1
}
}
}
}
struct MyScrollView: View {
@Binding var scrollTarget: Int?
var body: some View {
ScrollViewReader { proxy in
ScrollView {
ForEach(0..<100) { index in
Text("\(index)")
}
Color.clear.frame(height: 1)
.id(1)
}
.onChange(of: scrollTarget) { _, newTarget in
withAnimation {
proxy.scrollTo(newTarget)
}
}
}
}
}
#Preview {
ContentView()
}
一度Scroll to Bottomした後、手動で位置を変え再度Scroll to Bottomを試みても、二回目以降は onChanged{}
が呼ばれないためできない。
以下のように書いても、変化が早すぎるためかScrollされない。
...
Button("scroll") {
scrollTarget = nil
scrollTarget = 1
}
...
また、Scroll to Bottomを実現したいだけならscrollTarget
はMyScrollView
内部に隠蔽してしまいたい。