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) }
}
}
}
어레이 인덱스 사용
그indices
property는 숫자의 범위입니다.
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), 어레이 인덱스가 안정적이지 않음\.self
identity 패스를 합니다.
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
'programing' 카테고리의 다른 글
UILabel 글꼴 크기 동적 변경 (0) | 2023.04.09 |
---|---|
Swift는 String에 트리밍 방식이 있나요? (0) | 2023.04.09 |
Swift에서 URL을 여는 방법 (0) | 2023.04.09 |
데이터 테이블 에이잭스 호출 성공 시 함수를 호출한다. (0) | 2023.04.04 |
AngularJS 디렉티브 제한 A와 E (0) | 2023.04.04 |