在SwiftUI中获取ForEach中的索引

时间:2019-07-28 20:26:42

标签: swift swiftui

我有一个数组,我想遍历它,根据数组值初始化视图,并希望根据数组项索引执行操作

当我遍历对象时

B

所以,我尝试了另一种方法

C

但是第二种方法的问题是,例如,当我更改数组时,import是否遵循了

ForEach(array, id: \.self) { item in
  CustomView(item: item)
    .tapAction {
      self.doSomething(index) // Can't get index, so this won't work
    }
}
ForEach((0..<array.count)) { index in CustomView(item: array[index]) .tapAction { self.doSomething(index) } } 中的

视图不会更改,即使值已更改。我相信,发生这种情况是因为doSomething并没有改变。

有解决方案吗?预先感谢。

8 个答案:

答案 0 :(得分:23)

另一种方法是使用:

enumerated()

ForEach(Array(array.enumerated()), id: \.offset) { index, element in
  // ...
}

来源:https://alejandromp.com/blog/swiftui-enumerated/

答案 1 :(得分:14)

我需要一个更通用的解决方案,该解决方案可以处理所有类型的数据(实现RandomAccessCollection),还可以通过使用范围防止未定义的行为。
我结束了以下内容:

public struct ForEachWithIndex<Data: RandomAccessCollection, ID: Hashable, Content: View>: View {
    public var data: Data
    public var content: (_ index: Data.Index, _ element: Data.Element) -> Content
    var id: KeyPath<Data.Element, ID>

    public init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (_ index: Data.Index, _ element: Data.Element) -> Content) {
        self.data = data
        self.id = id
        self.content = content
    }

    public var body: some View {
        ForEach(
            zip(self.data.indices, self.data).map { index, element in
                IndexInfo(
                    index: index,
                    id: self.id,
                    element: element
                )
            },
            id: \.elementID
        ) { indexInfo in
            self.content(indexInfo.index, indexInfo.element)
        }
    }
}

extension ForEachWithIndex where ID == Data.Element.ID, Content: View, Data.Element: Identifiable {
    public init(_ data: Data, @ViewBuilder content: @escaping (_ index: Data.Index, _ element: Data.Element) -> Content) {
        self.init(data, id: \.id, content: content)
    }
}

extension ForEachWithIndex: DynamicViewContent where Content: View {
}

private struct IndexInfo<Index, Element, ID: Hashable>: Hashable {
    let index: Index
    let id: KeyPath<Element, ID>
    let element: Element

    var elementID: ID {
        self.element[keyPath: self.id]
    }

    static func == (_ lhs: IndexInfo, _ rhs: IndexInfo) -> Bool {
        lhs.elementID == rhs.elementID
    }

    func hash(into hasher: inout Hasher) {
        self.elementID.hash(into: &hasher)
    }
}

这样,问题中的原始代码就可以替换为:

ForEachWithIndex(array, id: \.self) { index, item in
  CustomView(item: item)
    .tapAction {
      self.doSomething(index) // Now works
    }
}

获取索引以及元素。

请注意,该API已镜像到SwiftUI的API-这意味着带有id参数的content闭包的初始化程序不是一个@ViewBuilder。 br /> 唯一的变化是id参数可见并且可以更改

答案 2 :(得分:7)

对于基于非零的数组,避免使用枚举,而是使用 zip:

ForEach(Array(zip(items.indices, items)), id: \.0) { index, item in
  // Add Code here
}

答案 3 :(得分:3)

以下方法的优点是,即使状态值发生更改,ForEach中的视图甚至也会更改:

struct ContentView: View {
    @State private var array = [1, 2, 3]

    func doSomething(index: Int) {
        self.array[index] = Int.random(in: 1..<100)
    }

    var body: some View {    
        let arrayIndexed = array.enumerated().map({ $0 })

        return List(arrayIndexed, id: \.element) { index, item in

            Text("\(item)")
                .padding(20)
                .background(Color.green)
                .onTapGesture {
                    self.doSomething(index: index)
            }
        }
    }
}

...这也可以用于例如删除最后的分隔线 在列表中:

struct ContentView: View {

    init() {
        UITableView.appearance().separatorStyle = .none
    }

    var body: some View {
        let arrayIndexed = [Int](1...5).enumerated().map({ $0 })

        return List(arrayIndexed, id: \.element) { index, number in

            VStack(alignment: .leading) {
                Text("\(number)")

                if index < arrayIndexed.count - 1 {
                    Divider()
                }
            }
        }
    }
}

答案 4 :(得分:1)

这对我有用:

{
  "type": "response",
  "@timestamp": "2019-07-24T19:41:20Z",
  "tags": [],
  "pid": 1,
  "method": "get",
  "statusCode": 200,
  "req": {
    "url": "/ui/fonts/inter_ui/Inter-UI-SemiBold.woff2",
    "method": "get",
    "headers": {
      "host": "0.0.0.0:5601",
      "connection": "keep-alive",
      "pragma": "no-cache",
      "cache-control": "no-cache",
      "origin": "http://0.0.0.0:5601",
      "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/74.0.3723.0 Safari/537.36",
      "accept": "*/*",
      "referer": "http://0.0.0.0:5601/login?next=%2Fs%2Finfrastructure%2Fapp%2Fkibana",
      "accept-encoding": "gzip, deflate"
    },
    "remoteAddress": "127.0.0.1",
    "userAgent": "127.0.0.1",
    "referer": "http://0.0.0.0:5601/login?next=%2Fs%2Finfrastructure%2Fapp%2Fkibana"
  },
  "res": {
    "statusCode": 200,
    "responseTime": 4,
    "contentLength": 9
  },
  "message": "GET /ui/fonts/inter_ui/Inter-UI-SemiBold.woff2 200 4ms - 9.0B"
}
{
  "type": "log",
  "@timestamp": "2019-07-24T19:43:18Z",
  "tags": [
    "reporting",
    "browser-driver",
    "chromium-driver-factory",
    "headless-chromium-driver",
    "error"
  ],
  "pid": 1,
  "message": "waitForSelector [data-shared-item],[data-shared-items-count] failed on http://0.0.0.0:5601/login?next=%2Fs%2Finfrastructure%2Fapp%2Fkibana#/visualize/edit/6f653c90-8ef8-11e9-bcc9-fbde2741907a?_g=()&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(vis:(legendOpen:!t)),vis:(aggs:!((enabled:!t,id:'1',params:(field:rabbitmq.queue.messages.total.count),schema:metric,type:max),(enabled:!t,id:'2',params:(drop_partials:!f,extended_bounds:(),field:'@timestamp',interval:auto,min_doc_count:1,useNormalizedEsInterval:!t),schema:segment,type:date_histogram),(enabled:!t,id:'3',params:(field:rabbitmq.queue.name,missingBucket:!f,missingBucketLabel:Missing,order:desc,orderBy:'1',otherBucket:!f,otherBucketLabel:Other,size:15),schema:group,type:terms)),params:(addLegend:!t,addTimeMarker:!t,addTooltip:!t,categoryAxes:!((id:CategoryAxis-1,labels:(show:!t,truncate:100),position:bottom,scale:(type:linear),show:!t,style:(),title:(),type:category)),grid:(categoryLines:!t,valueAxis:!n),legendPosition:right,seriesParams:!((data:(id:'1',label:'Max%20rabbitmq.queue.messages.total.count'),drawLinesBetweenPoints:!t,interpolate:cardinal,mode:normal,show:true,showCircles:!t,type:line,valueAxis:ValueAxis-1)),times:!(),type:line,valueAxes:!((id:ValueAxis-1,labels:(filter:!f,rotate:0,show:!t,truncate:100),name:LeftAxis-1,position:left,scale:(mode:normal,type:linear),show:!t,style:(),title:(text:'Max%20rabbitmq.queue.messages.total.count'),type:value))),title:'RabbitMQ%20Queued%20Messages',type:line))&forceNow=2019-07-24T19:41:16.435Z"
}

答案 5 :(得分:1)

我为此基于Stone出色的解决方案创建了专用的View

struct EnumeratedForEach<ItemType, ContentView: View>: View {
    let data: [ItemType]
    let content: (Int, ItemType) -> ContentView
    
    init(_ data: [ItemType], @ViewBuilder content: @escaping (Int, ItemType) -> ContentView) {
        self.data = data
        self.content = content
    }
    
    var body: some View {
        ForEach(Array(self.data.enumerated()), id: \.offset) { idx, item in
            self.content(idx, item)
        }
    }
}

现在您可以像这样使用它:

EnumeratedForEach(items) { idx, item in
    ...
}

答案 6 :(得分:0)

这是一个简单的解决方案,尽管对上述解决方案效率不高。

在“点击操作”中,通过项目

.tapAction {

   var index = self.getPosition(item)

}

然后创建一个函数,通过比较id查找该项目的索引

func getPosition(item: Item) -> Int {

  for i in 0..<array.count {
        
        if (array[i].id == item.id){
            return i
        }
        
    }
    
    return 0
}

答案 7 :(得分:0)

2021 解决方案如果您使用基于非零的数组,请避免使用枚举:

ForEach(array.indices,id:\.self) { index in
    VStack {
        Text(array[index].name)
            .customFont(name: "STC", style: .headline)
            .foregroundColor(Color.themeTitle)
        }
    }
}