6

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

我有一个用于列表的一行的行模型(我说的是列表,即使我实际上通常使用一个名为 Collection 的定制元素,它是一个带有 HStack 和 VStack 的列表来模拟UICollectionView

在行模型视图中,我只能为 NavigationLink 指定一个目的地

我能想到的一个解决方案是获取被点击的列表项的索引并基于该索引打开某个视图。但我无法让它工作。

我基本上需要 List 中的每个元素打开一个不同的视图。

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


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

3 回答 3

12

虽然公认的答案解决了问题,但它并不完全是一个优雅的解决方案。为了清理我们的代码,最好调用一个返回所需目的地的方法。这是我解决它的方法:

请注意以下事项:我在导航项模型中添加了一个名为 的属性destination,将其类型设置为Any,然后使用nameOfTypeToNavigateTo.self. 通过这种方式,我们避免了需要使用单元格标签之类的东西来确定哪个单元格被点击了。

struct NavigationCell: View {
    var navigationItem: NavigationItem
    var body: some View {
        NavigationLink(destination: getDestination(from: navigationItem)) {
            HStack {
                Text(navigationItem.name)
            }
        }
    }
    
    func getDestination(from navItem: NavigationItem) -> AnyView {
        if navItem.destination is ZoneList.Type {
            return AnyView(ZonesList())
        } else {
            return AnyView(ListStyles())
        }
    }
}

这里的诀窍是确保您的返回类型是AnyView. 直觉上你会认为返回类型getDestination()应该是some ViewsinceView是我们在 SwiftUI 中构建的视图所遵循的协议。但是返回的方法some View只能返回 ONE 类型——碰巧符合的单一类型View。但这不是我们需要的。我们正在尝试创建一种方法,该方法能够根据我们要导航到的视图返回各种类型的任何一种。AnyView是解决方案,并给了我们一个type erased view.

这是AppleAnyView文档。这是一篇关于如何以其他方式使用它的有用文章:

于 2020-06-27T14:47:27.190 回答
8

我假设您想查看元素并确定要显示的视图,以下实现了这一点。您也可以为此使用枚举。

元素中的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使用which 无法正常工作的示例:

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)
        }
    }
}
于 2019-08-22T09:58:25.280 回答
0

以下代码将用户单击列表单元格导航到Tutor listStudent list基于topicId

struct Topic: Identifiable
{
    var id = UUID()
    var topicId : Int
    var title   : String
    var desc    : String
}

struct TopicCell: View
{
    let topic : Topic
    
    var body: some View
    {
        NavigationLink(destination: getDestination(topic: topic))
        {
            VStack(alignment: .leading)
            {
                Text(topic.title)
                    .foregroundColor(.blue)
                    .font(.title)
                
                Text(topic.desc)
                    .foregroundColor(.white)
                    .font(.subheadline)
            }
        }
    }
    
    func getDestination(topic: Topic) -> AnyView
    {
        if topic.topicId == 0 {
            return AnyView(TutorList(topic: topic))
        } else {
            return AnyView(StudentList(topic: topic))
        }
    }
}
于 2021-05-26T12:40:14.687 回答