In the app I’m working on I have a details view that uses ScrollView to display a bunch of information. I want to use a List inside that ScrollView to display some information. I want to use a list because of some of the built-in features (moving rows, swipe actions, showing NavigationLinks with the arrow). Since the List view is incompatible with ScrollView I’ve implemented my own list view that uses a ForEach and some styling to make it look like a normal List. I want my custom list to be able to display NavigationLinks the same way List does (the whole row becomes clickable and there is a grey arrow and the right).
So there seems to be 2 different ways my problem could be solved: 1. If I know how to calculate how much space the list is going to need then I can set the height manually (but as of now I don’t know how to calculate if a row is going to be higher than the minimum height). 2. Display NavigationLinks the same as the built-in List and change the other aspects of my design.
This is my custom list view so far (the most relevant code is in the else statement):
struct DynamicList<SelectionValue: Identifiable, Content: View>: View {
@Environment(.defaultMinListRowHeight) private var minRowHeight
@Binding var values: [SelectionValue]
var canMove = false
@ViewBuilder var content: (SelectionValue) -> Content
var body: some View {
if canMove {
List {
ForEach(values) { value in
content(value)
.lineLimit(1)
}
.onMove(perform: move)
}
.listStyle(.plain)
.scrollDisabled(true)
.frame(minHeight: minRowHeight * CGFloat(values.count))
.card()
} else {
VStack(alignment: .leading, spacing: 0) {
ForEach(values) { value in
HStack {
content(value)
.padding(.vertical, 12)
.padding(.horizontal, 20)
if hasNavigationLink() {
Spacer()
Image(systemName: .chevronRight)
.foregroundColor(Color.systemGray3)
.bold()
.font(.system(size: 14))
.padding(.trailing)
}
}
.contentShape(Rectangle())
.buttonStyle(PlainButtonStyle())
Divider()
.padding(.leading)
}
}
.card()
}
}
}
extension DynamicList {
init(_ values: [SelectionValue], @ViewBuilder _ content: @escaping (SelectionValue) -> Content) {
_values = .constant(values)
self.content = content
}
init(_ values: Binding<[SelectionValue]>, canMove: Bool, @ViewBuilder _ content: @escaping (SelectionValue) -> Content) {
_values = values
self.canMove = canMove
self.content = content
}
func move(from source: IndexSet, to destination: Int) {
values.move(fromOffsets: source, toOffset: destination)
}
func hasNavigationLink() -> Bool {
let mirror = Mirror(reflecting: content)
if String(describing: mirror.subjectType).contains("NavigationLink") {
return true
}
return false
}
}
Jordan Herget is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.