Change NavigationLink destination conditionally in SwiftUI

别说谁变了你拦得住时间么 提交于 2020-05-31 03:32:17

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!