programing

Swift에서 ForEach에 인덱스 가져오기UI

stoneblock 2023. 4. 9. 21:02

Swift에서 ForEach에 인덱스 가져오기UI

어레이가 있으며 어레이 값을 기반으로 보기를 초기화하고 어레이 항목 인덱스를 기반으로 작업을 수행하려고 합니다.

사물을 통해 반복할 때

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다음 작업을 수행합니다.

self.array = [1,2,3]

의 뷰ForEach값이 변경되어도 변경되지 않습니다.그런 일이 일어나는 건array.count변하지 않았어요.

이에 대한 해결책이 있나요?

또 다른 접근방식은 다음과 같습니다.

열거()

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

출처 : https://alejandromp.com/blog/swiftui-enumerated/

이것으로 충분합니다.

범위 및 카운트 사용

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

    func doSomething(index: Int) {
        self.array = [1, 2, 3]
    }
    
    var body: some View {
        ForEach(0..<array.count) { i in
          Text("\(self.array[i])")
            .onTapGesture { self.doSomething(index: i) }
        }
    }
}

어레이 인덱스 사용

indicesproperty는 숫자의 범위입니다.

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

    func doSomething(index: Int) {
        self.array = [1, 2, 3]
    }
    
    var body: some View {
        ForEach(array.indices) { i in
          Text("\(self.array[i])")
            .onTapGesture { self.doSomething(index: i) }
        }
    }
}

나는 주로 사용한다.enumerated한 켤레 사다index그리고.element와 함께element처럼id

ForEach(Array(array.enumerated()), id: \.element) { index, element in
    Text("\(index)")
    Text(element.description)
}

보다 재사용 가능한 컴포넌트에 대해서는, 다음의 문서를 참조해 주세요.https://onmyway133.com/posts/how-to-use-foreach-with-indices-in-swiftui/

모든 종류의 데이터에 대응할 수 있는 보다 범용적인 솔루션이 필요했습니다.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는 Swift API와 미러링 됩니다.UI - 즉, 이 이니셜라이저는,id파라미터의content클로징은@ViewBuilder.
그것으로부터의 유일한 변화는id파라미터가 표시되며 변경할 수 있습니다.

제로 베이스가 아닌 어레이의 경우 열거형 사용을 피합니다.대신 zip을 사용합니다.

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

전용으로 작성했습니다.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(zip(data.indices, data)), id: \.0) { idx, item in
            content(idx, item)
        }
    }
}

이제 다음과 같이 사용할 수 있습니다.

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

ForEach스위프트UI는 for loop과 동일하지 않습니다.실제로 구조 정체성이라고 불리는 기능을 합니다.의 문서ForEach상태:

/// It's important that the `id` of a data element doesn't change, unless
/// SwiftUI considers the data element to have been replaced with a new data
/// element that has a new identity.

즉, 인덱스, 열거형 또는 새로운 어레이를ForEach.그ForEach식별 가능한 항목의 실제 배열을 제공해야 합니다.이건 정말 스위프트다UI는 데이터와 일치하도록 주위의 행을 애니메이션화할 수 있으며, 0의 행을 1로 이동하면 인덱스가 0인 것과 같이 인디케이터에서는 동작할 수 없습니다.

인덱스를 가져오는 문제를 해결하려면 다음과 같이 인덱스를 검색하면 됩니다.

ForEach(items) { item in
  CustomView(item: item)
    .tapAction {
      if let index = array.firstIndex(where: { $0.id == item.id }) {
          self.doSomething(index) 
      }
    }
}

Apple이 Scrumdinger 샘플 앱 튜토리얼에서 이것을 하고 있는 것을 알 수 있습니다.

guard let scrumIndex = scrums.firstIndex(where: { $0.id == scrum.id }) else {
    fatalError("Can't find scrum in array")
}

다음 접근법의 장점은 상태 값이 변경되어도 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()
                }
            }
        }
    }
}

2021년 솔루션은 제로 베이스가 아닌 어레이를 사용하는 경우 열거된 어레이를 사용하지 않도록 합니다.

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

Swift에서 인덱스를 가져오려면UI의 ForEqual 루프에서는 클로저의 단축형 인수 이름을 사용할 수 있습니다.

@State private var cars = ["Aurus","Bentley","Cadillac","Genesis"]

var body: some View {
    NavigationView {
        List {
            ForEach(Array(cars.enumerated()), id: \.offset) {

                Text("\($0.element) at \($0.offset) index")
            }
        }
    }
}

결과:

//   Aurus at 0 index
//   Bentley at 1 index
//   Cadillac at 2 index
//   Genesis at 3 index


추신.

처음에는 모든 Swift 개발자들이 익숙한 "흔한" 표정으로 답변을 올렸는데, @loremipsum 덕분에 변경했습니다.WWDC 2021 Demystify Swift에 기재된 바와 같이UI 비디오(시간 33:40), 어레이 인덱스가 안정적이지 않음\.selfidentity 패스를 합니다.

ForEach(0 ..< cars.count, id: \.self) {     // – NOT STABLE
    Text("\(cars[$0]) at \($0) index")
}

위의 솔루션에는 매우 비효율적이지만 간단한 해결책이 있습니다.

탭 동작에서 항목을 통과합니다.

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

다음 방법을 사용할 수 있습니다.

.enumerated()

Swift 매뉴얼:

쌍의 시퀀스(n, x)를 반환합니다.여기서 n은 0에서 시작하는 연속 정수를 나타내고 x는 시퀀스의 요소를 나타냅니다.

var elements: [String] = ["element 1", "element 2", "element 3", "element 4"]

ForEach(Array(elements.enumerated()), id: \.element) { index, element in
  Text("\(index) \(element)")
}

Extension을 사용하여 간단한 솔루션을 찾았습니다.

struct ForEachIndex<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(zip(data.indices, data)), id: \.0) { idx, item in
            content(idx, item)
        }
    }
}

사용방법:

ForEachIndex(savedModel) { index, model in
    //Do you work here
}

처럼 '아예'를 해도 됩니다.array.indices이 경우 사용 중인 인덱스는 배열의 마지막 요소에서 시작됩니다. 이 문제를 해결하려면 다음을 사용해야 합니다.array.indices.reversed()한 For Each 、 를 ID 、 를를를를 。하다

ForEach(array.indices.reversed(), id:\.self) { index in }

언급URL : https://stackoverflow.com/questions/57244713/get-index-in-foreach-in-swiftui