I implemented a list control. I want to scroll the row to center when it is selected. My row contains one image and two texts. Below is my code.
var body: some View {
ScrollViewReader { scrollView in
List(videos, id: \.id) { video in
VStack(alignment: .leading) {
WebImage(url: URL(string: video.thumbnailURL))
.resizable()
.placeholder {
Rectangle().foregroundColor(.clear)
}
.transition(.fade(duration: 0.5))
.aspectRatio(contentMode: .fit)
.frame(minHeight:100.0, alignment: .center)
Text(video.title)
.foregroundColor(.white)
.padding(.horizontal, 5)
.lineLimit(1)
.font(Font.custom("Montserrat Bold", size: 14))
Text(video.artist)
.foregroundColor(.white)
.padding(.horizontal, 5)
.lineLimit(1)
.font(Font.custom("Montserrat Medium", size: 14))
.padding(.bottom, 5)
}.background(Color.black)
.listRowBackground(Color.clear)
.onTapGesture {
selectedVideoId = video.id
delegate?.videoSelected(video: video)
}
.border((selectedVideoId == video.id) ? Color(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003) : Color.clear, width: (selectedVideoId == video.id) ? 1.5 : 0.0)
}.padding(.horizontal, 20.0)
.padding(.bottom, 10.0)
.background(Color.clear)
.listStyle(.plain)
.onChange(of: selectedVideoId) { vid in
scrollView.scrollTo(1, anchor: .top)
}
}
}
The .onChange(of: selectedVideoId)
closure gets called when a row is selected. But it doesn't scroll to the first row.
ScrollViewReader
works with the id
of your model items. You should not use the key-path-based initializer id: \.id
.
Conform your model type to Identifiable
:
struct Video: Identifiable {
let id: UUID = UUID()
let title: String
let artist: String
// [...] whatever else you have here
}
Use your Identifiable
model items, assign the id
to each view & scrollTo(selectedVideoId)
:
var body: some View {
VStack {
ScrollViewReader { scrollView in
List(v.videos) { video in // << use id here
VStack(alignment: .leading) {
WebImage(url: URL(string: video.thumbnailURL))
.resizable()
.placeholder {
Rectangle().foregroundColor(.clear)
}
.transition(.fade(duration: 0.5))
.aspectRatio(contentMode: .fit)
.frame(minHeight:100.0, alignment: .center)
Text(video.title)
.foregroundColor(.white)
.padding(.horizontal, 5)
.lineLimit(1)
.font(Font.custom("Montserrat Bold", size: 14))
Text(video.artist)
.foregroundColor(.white)
.padding(.horizontal, 5)
.lineLimit(1)
.font(Font.custom("Montserrat Medium", size: 14))
.padding(.bottom, 5)
}
.id(video.id) // << assign the id to each view
.background(Color.black)
.listRowBackground(Color.clear)
.onTapGesture {
selectedVideoId = video.id
delegate?.videoSelected(video: video)
}
.border((selectedVideoId == video.id) ? Color(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003) : Color.clear, width: (selectedVideoId == video.id) ? 1.5 : 0.0)
}
.padding(.horizontal, 20.0)
.padding(.bottom, 10.0)
.background(Color.clear)
.listStyle(.plain)
.onChange(of: selectedVideoId) { vid in
print("new selected: \(selectedVideoId)")
withAnimation {
scrollView.scrollTo(selectedVideoId, anchor: .top)
// << scroll to id
}
}
}
}
.padding()
}