问题
I have a view with a List and depending on the item of the list I have clicked on, I need to open a different NavigationLink.
I have a row model for one row of the List (I'm saying List even though I'm actually usually using a custom made element called Collection, which is a List with HStack and VStack together to emulate the UICollectionView)
In the row model view I can only specify one destination for the NavigationLink.
A solution I can think of is getting the index of the item of the list that was clicked and opening a certain view based on that. But I was not able to get it to work.
I basically need that each element in the List opens a different view.
Any help is appreciated! Thank you ! :)
GroupDetail.swift
import SwiftUI
// data displayed in the collection view
var itemsGroupData = [
ItemsGroupModel("Projects"), // should open ProjectsView()
ItemsGroupModel("People"), //should open PeopleView()
ItemsGroupModel("Agenda"), //should open AgendaView()
ItemsGroupModel("Name") //should open NameView()
]
// main view where the collection view is shown
struct GroupDetail: View {
var body: some View {
// FAMOUS COLLECTION ELEMENT
Collection(itemsGroupData, columns: 2, scrollIndicators: false) { index in
ItemsGroupRow(data: index)
}
}
}
// model of the data
struct ItemsGroupModel: Identifiable {
var id: UUID
let title: String
init(_ title: String) {
self.id = UUID()
self.title = title
}
}
// row type of the collection view
struct ItemsGroupRow: View {
var body: some View {
// This model is for one row
// I need to specify what view to open depending on what item of the collection view was selected (clicked on)
NavigationLink(destination: ProjectsView()) { // open ProjectsView only if the user clicked on the item "Projects" of the list etc..
Text(data.title)
}
}
}
-----------------------------------------------------------------------
Collection.swift
// this custom struct creates the UIKit equivalent of the UICollectionView
// it uses a HStack and a VStack to make columns and rows
import SwiftUI
@available(iOS 13.0, OSX 10.15, *)
public struct Collection<Data, Content>: View
where Data: RandomAccessCollection, Content: View, Data.Element: Identifiable {
private struct CollectionIndex: Identifiable { var id: Int }
// MARK: - STORED PROPERTIES
private let columns: Int
private let columnsInLandscape: Int
private let vSpacing: CGFloat
private let hSpacing: CGFloat
private let vPadding: CGFloat
private let hPadding: CGFloat
private let scrollIndicators: Bool
private let axisSet: Axis.Set
private let data: [Data.Element]
private let content: (Data.Element) -> Content
// MARK: - COMPUTED PROPERTIES
private var actualRows: Int {
return data.count / self.actualColumns
}
private var actualColumns: Int {
return UIDevice.current.orientation.isLandscape ? columnsInLandscape : columns
}
// MARK: - INIT
public init(_ data: Data,
columns: Int = 2,
columnsInLandscape: Int? = nil,
scrollIndicators: Bool = true,
axisSet: Axis.Set = .vertical,
vSpacing: CGFloat = 10,
hSpacing: CGFloat = 10,
vPadding: CGFloat = 10,
hPadding: CGFloat = 10,
content: @escaping (Data.Element) -> Content) {
self.data = data.map { $0 }
self.content = content
self.columns = max(1, columns)
self.columnsInLandscape = columnsInLandscape ?? max(1, columns)
self.vSpacing = vSpacing
self.hSpacing = hSpacing
self.vPadding = vPadding
self.hPadding = hPadding
self.scrollIndicators = scrollIndicators
self.axisSet = axisSet
}
// MARK: - BODY
public var body : some View {
GeometryReader { geometry in
ScrollView(self.axisSet, showsIndicators: self.scrollIndicators) {
if self.axisSet == .horizontal {
HStack(alignment: .center, spacing: self.hSpacing) {
ForEach((0 ..< self.actualRows).map { CollectionIndex(id: $0) }) { row in
self.createRow(row.id, geometry: geometry)
}
}
} else {
VStack(spacing: self.vSpacing) {
ForEach((0 ..< self.actualRows).map { CollectionIndex(id: $0) }) { row in
self.createRow(row.id * self.actualColumns, geometry: geometry)
}
// LAST ROW HANDLING
if (self.data.count % self.actualColumns > 0) {
self.createRow(self.actualRows * self.actualColumns, geometry: geometry, isLastRow: true)
.padding(.bottom, self.vPadding)
}
}
}
}
}
}
// MARK: - HELPER FUNCTIONS
private func createRow(_ index: Int, geometry: GeometryProxy, isLastRow: Bool = false) -> some View {
HStack(spacing: self.hSpacing) {
ForEach((0 ..< actualColumns).map { CollectionIndex(id: $0) }) { column in
self.contentAtIndex(index + column.id)
.frame(width: self.contentWidthForGeometry(geometry))
.opacity(!isLastRow || column.id < self.data.count % self.actualColumns ? 1.0 : 0.0)
}
}
}
private func contentAtIndex(_ index: Int) -> Content {
// (Addressing the workaround with transparent content in the last row) :
let object = index < data.count ? data[index] : data[data.count - 1]
return content(object)
}
private func contentWidthForGeometry(_ geometry: GeometryProxy) -> CGFloat {
let hSpacings = hSpacing * (CGFloat(self.actualColumns) - 1)
let width = geometry.size.width - hSpacings - hPadding * 2
return width / CGFloat(self.actualColumns)
}
}
回答1:
I assume you want to look at the element and to determine what view to show, the following achieves that. You can also use an enum for this.
The Collection
passes in the element, so you can use that and a conditional:
struct ProjectView: View {
var row: ItemsGroupModel
var body: some View {
Text("Project title = \(row.title)")
}
}
struct NonProjectView: View {
var row: ItemsGroupModel
var body: some View {
Text("Non-Project title = \(row.title)")
}
}
// row type of the collection view
struct ItemsGroupRow: View {
var data: ItemsGroupModel
var body: some View {
// This model is for one row
// I need to specify what view to open depending on what item of the collection view was selected (clicked on)
NavigationLink(destination: {
VStack{
if data.title.contains("project") {
ProjectView(row: data)
} else {
NonProjectView(row: data)
}
}
}()) { // open ProjectsView only if the user clicked on the item "Projects" of the list etc..
Text(data.title)
}
}
}
Note: I think the collection does have some bugs.
Example using enum
which does not work correctly:
import SwiftUI
struct ConditionalNavigationLinkView: View {
var body: some View {
GroupDetail()
}
}
// data displayed in the collection view
var itemsGroupData = [
ItemsGroupModel(.project), // should open ProjectsView()
ItemsGroupModel(.people), //should open PeopleView()
ItemsGroupModel(.agenda), //should open AgendaView()
ItemsGroupModel(.name) //should open NameView()
]
// main view where the collection view is shown
struct GroupDetail: View {
var body: some View {
NavigationView{
// FAMOUS COLLECTION ELEMENT
Collection(itemsGroupData, columns: 2, scrollIndicators: false) { row in
ItemsGroupRow(data: row)
}
}
}
}
enum ItemsGroupModelType: String {
case project = "Projects"
case people = "People"
case agenda = "Agenda"
case name = "Name"
}
// model of the data
struct ItemsGroupModel: Identifiable {
var id: UUID
let type: ItemsGroupModelType
init(_ type: ItemsGroupModelType) {
self.id = UUID()
self.type = type
}
}
struct ProjectView: View {
var row: ItemsGroupModel
var body: some View {
Text("Projects \(row.type.rawValue)")
}
}
struct NameView: View {
var row: ItemsGroupModel
var body: some View {
Text("NameView \(row.type.rawValue)")
}
}
struct PeopleView: View {
var row: ItemsGroupModel
var body: some View {
Text("PeopleView \(row.type.rawValue)")
}
}
struct AgendaView: View {
var row: ItemsGroupModel
var body: some View {
Text("AgendaView \(row.type.rawValue)")
}
}
// row type of the collection view
struct ItemsGroupRow: View {
func printingEmptyView() -> EmptyView {
print("type: \(data.type.rawValue)")
return EmptyView()
}
var data: ItemsGroupModel
var body: some View {
// This model is for one row
// I need to specify what view to open depending on what item of the collection view was selected (clicked on)
NavigationLink(destination: {
//print("Hello")
VStack{
self.printingEmptyView()
if data.type == .project {
ProjectView(row: data)
}
if data.type == .people {
PeopleView(row: data)
}
if data.type == .agenda {
AgendaView(row: data)
}
if data.type == .name {
NameView(row: data)
}
}
}()) { // open ProjectsView only if the user clicked on the item "Projects" of the list etc..
Text(data.type.rawValue)
}
}
}
来源:https://stackoverflow.com/questions/57606290/change-navigationlink-destination-conditionally-in-swiftui