ios - 在SwiftUI中有条件地更改NavigationLink目标

标签 ios swiftui swiftui-list

我有一个带有列表的 View ,根据我单击的列表的项目,我需要打开一个不同的NavigationLink。

我对列表的一行有一个行模型(我说的是List,尽管我实际上通常使用的是一个名为Collection的定制元素,这是一个将HStack和VStack结合在一起的List来模拟 UICollectionView )

在行模型 View 中,我只能为NavigationLink指定一个目标

我能想到的解决方案是获取被单击列表的项目的索引,并基于该索引打开某个 View 。但是我无法使其正常工作。

我基本上需要列表中的每个元素打开一个不同的 View 。

任何帮助表示赞赏!
谢谢! :)

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)
  }
}

最佳答案

我假设您要查看该元素并确定要显示的 View ,以下内容可以实现这一点。您也可以为此使用一个枚举。
Collection传入元素,因此您可以使用它和一个条件:

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)
        }
    }
}

注意:我认为该集合确实存在一些错误。

使用enum的示例无法正常工作:
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)
        }
    }
}

关于ios - 在SwiftUI中有条件地更改NavigationLink目标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57606290/

相关文章:

iphone - 如何仅当用户是使用 canEditRowAtIndexPath : 发布评论的用户时才允许用户删除评论

objective-c - 使用 CoreData 关系计算摘要

ios - SwiftUI:什么是@AppStorage 属性包装器

reactive-programming - 为什么我的 SwiftUI 列表行并不总是在内部更新

SwiftUI 列表背景色

swift - 如何将上下文菜单应用于 SwiftUI 列表行中的按钮?

ios - 再现 UITextField 的边框

ios - 使用高阶 Swift 函数的潜在内存泄漏

swiftui - 如何收到符合协议(protocol)的 ObservedObject 更改的通知

ios - 带有内容框架更新关闭的 SwiftUI ScrollView