This little code is showing articles from an API with 2 columns with scrollview
on ArticleView
. Unfortunately after the 6th loadMoreContentIfNeeded
it gives me hard time with this error
Thread 1: Fatal error: each layout item may only occur once
I have tried adding .id(UUID())
on my code to avoid id problems but it did not help either. I also observed that this error happens in LazyVStavk too. If you change LazyVStack to VStack this problem won't occur. But there is not anything in SwiftUI called VGrid
.
struct ArticleView: View {
@ObservedObject var service = ArticleService()
let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 0), count: 2)
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing:0) {
ForEach(Array(service.articlePost.enumerated()), id: \.1.article_id) { i, post in
ArticleListElementView(articlePost: post, cellNumber: i)
.id(UUID())
.onAppear {
self.service.loadMoreContentIfNeeded(currentItem: post)
}
.padding(.all, 20)
.onTapGesture {
self.shown = true
self.selectedArticle = post
}
}
}
}
private func loadMoreContent() {
guard !isLoadingPage && canLoadMorePages else {
return
}
isLoadingPage = true
let parameters = ["index": "articles", "start" : "\(currentPage)"]
if let url = URL(string: "http:/v2/search/") {
let session = URLSession(configuration: .default)
var request = URLRequest(url:url)
request.httpMethod = "POST"
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
session.dataTaskPublisher(for: request as URLRequest)
.map(\.data)
.decode(type: ArticleResults.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.handleEvents(receiveOutput: { response in
self.isLoadingPage = false
self.currentPage += 1
})
.map({ response in
return self.articlePost + response.articles
})
.catch({ _ in Just(self.articlePost) })
.assign(to: &$articlePost)
}
}
func loadMoreContentIfNeeded(currentItem item: ArticlePost?) {
guard let item = item else {
loadMoreContent()
return
}
let thresholdIndex = articlePost.index(articlePost.endIndex, offsetBy: -5)
if articlePost.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
loadMoreContent()
}
}
I can only point you to the issue, because there is still some relevant code missing.
The problem lies in your ForEach or your Array. You are identifying content by
id: \.1.article_id
. However, I guess! in your array there are items which have the same article id.LazyVGrid
requires your content to have an explicit uniqueid
, whereVStack
doesn't.As I don't know your Array or
loadMoreContentIfNeeded
function, I can not point to your issue why there are duplicated items.Here is an example to show you the difference:
This will crash, because service contains "Test123" twice and you identity it by their content
Remove one "Test123" and it will work fine.