How to Get the Index of the Element in the List in Swiftui When the List Is Populated with the Array

How to get the index of the element in the List in SwiftUI when the List is populated with the array?

This can be done using using .enumerated. For your MenuItem values it will be as follows

List {
ForEach(Array(menuItems.enumerated()), id: \.1.id) { (index, textItem) in
// do with `index` anything needed here
Text(textItem.text)
}
}

SwiftUI - How to get the selected index of a list when you have a search?

The solution by Aperi is not good, because the only elements tappable will be the texts, not the cells.

The correct answer is to wrap the Text element as a view of a Button, like shown on this video...

Button(action: {
// do something()
}) {
Text(item.term!)
}

Scroll to bottom of a list populated with CoreData in SwiftUI

scrollTo() works by receiving an identifier, not an index.

Using array works because 10 is contained in array, but 10 cannot match item.

I would suggest you add an ID String property to your objects (I think you could even use object.uriRepresentation()) tag your views with that identifier, and now you'll be able to use scrollTo.

private var items: FetchedResults<Item>

NavigationView {
ScrollViewReader { (proxy: ScrollViewProxy) in
List{
ForEach(items) {item in
Text(item.text!)
.id(item.id)
}
}
Button("Tap to scroll") {
proxy.scrollTo(items[10].id, anchor: .top)
}
}
}
}

How to get on screen rows from List in SwiftUI?

You can use onAppear and onDisappear functions to maintain a list of visible rows and then use these to find a visible index.

struct ContentView: View {
let rows = (Unicode.Scalar("A").value...Unicode.Scalar("Z").value)
.map { String(Unicode.Scalar($0)!) }

@State var visibleRows: Set<String> = []

var body: some View {
List(rows, id: \.self) { row in
HStack {
Text(row)
.padding(40)
.onAppear { self.visibleRows.insert(row) }
.onDisappear { self.visibleRows.remove(row) }
}
Spacer()
Text(self.getVisibleIndex(for: row)?.description ?? "")
}
}

func getVisibleIndex(for row: String) -> Int? {
visibleRows.sorted().firstIndex(of: row)
}
}

SwiftUI List .onDelete: Index out of range

This is a SwiftUI bug reported in Deleting list elements from SwiftUI's List.

The solution is to use the extension from here that prevents accessing invalid bindings:

struct Safe<T: RandomAccessCollection & MutableCollection, C: View>: View {

typealias BoundElement = Binding<T.Element>
private let binding: BoundElement
private let content: (BoundElement) -> C

init(_ binding: Binding<T>, index: T.Index, @ViewBuilder content: @escaping (BoundElement) -> C) {
self.content = content
self.binding = .init(get: { binding.wrappedValue[index] },
set: { binding.wrappedValue[index] = $0 })
}

var body: some View {
content(binding)
}
}
List {
ForEach(vm.steps.indices, id: \.self) { index in
Safe(self.$vm.steps, index: index) { binding in
TheSlider(value: binding.theValue, index: vm.steps[index].theIndex)
}
}
.onDelete(perform: { indexSet in
self.vm.removeStep(index: indexSet)
})
}

ForEach - print both an item and numerical value

Try using enumerated

ForEach(Array(recipeDirections.enumerated()), id: \.offset){ (index,recipe) in

The index will be 0,1,2, according to array numbers.

SwiftUI get next item in ForEach loop

you could try adding a id to your struct Item, such as:

struct Item: Identifiable, Decodable, Hashable {
let id = UUID()
let card: Card
}

and then use:

VStack {
ForEach(item.cards, id: \.self) { theItem in
switch theItem.card.size {
case .Large:
LargeCard(card: theItem.card, viewModel: CardViewModel())
case .Medium:
MediumCard(card: theItem.card, viewModel: CardViewModel())
case .Small:
HStack(spacing: 16) {
SmallCard(card: theItem.card, viewModel: CardViewModel())
// here use the id to find the next item
if let ndx = item.cards.firstIndex(where: {$0.id == theItem.id}) {
if ndx + 1 < item.cards.count {
let nextItem = item.cards[ndx + 1]
if nextItem.card.size == .Small {
SmallCard(card: nextItem.card, viewModel: CardViewModel())
}
}
}
}
case .none:
Text("No more.")
}
}
}

You could also use enumerated as mentioned in the comments, such as:

VStack {
ForEach(Array(item.cards.enumerated()), id: \.offset) { index, theItem in
switch theItem.card.size {
case .Large:
LargeCard(card: theItem.card, viewModel: CardViewModel())
case .Medium:
MediumCard(card: theItem.card, viewModel: CardViewModel())
case .Small:
HStack(spacing: 16) {
SmallCard(card: theItem.card, viewModel: CardViewModel())
// here use the index to find the next item
if index + 1 < item.cards.count {
let nextItem = item.cards[index + 1]
if nextItem.card.size == .Small {
SmallCard(card: nextItem.card, viewModel: CardViewModel())
}
}
}
case .none:
Text("No more.")
}
}
}

Note, it looks like you should be using .environmentObject(viewModel) to pass
a single CardViewModel() to the views, instead of creating a new CardViewModel() each time.

SwiftUI trouble with index out of range

As Rob Napier mentioned, the issue is that you're accessing the array index before the array is populated.

I'd suggest a couple of improvements to your code. Also, instead of maintaining separate arrays (names, descriptions, ...) you can create a struct to hold all the properties in one place. This will allow you to use just one array for your items.

struct Item {
let name: String
let description: String
}
class Fetch: ObservableObject {
@Published var items: [Item] = [] // a single array to hold your items, empty at the beginning
@Published var loading = false // indicates whether loading is in progress

func longTask() {
loading = true // start fetching, set to true
let db = Firestore.firestore()
db.collection("Flipside").getDocuments { snapshot, err in
if let err = err {
print("Error getting documents: \(err)")
DispatchQueue.main.async {
self.loading = false // loading finished
}
} else {
let items = snapshot!.documents.map { document in // use `map` to replace `snapshot!.documents` with an array of `Item` objects
let name = document.get("Name") as! String
let description = document.get("Description") as! String
print("Names: ", name)
print("Descriptions: ", description)
return Item(name: name, description: description)
}
DispatchQueue.main.async { // perform assignments on the main thread
self.items = items
self.loading = false // loading finished
}
}
}
}
}
struct ContentView: View {
@StateObject private var fetch = Fetch() // use `@StateObject` in iOS 14+

var body: some View {
ZStack {
if fetch.loading { // when items are being loaded, display `LoadingView`
LoadingView()
} else if fetch.items.isEmpty { // if items are loaded empty or there was an error
Text("No items")
} else { // items are loaded and there's at least one item
Text(fetch.items[0].name)
.bold()
}
}
.onAppear {
self.fetch.longTask()
}
}
}

Note that accessing arrays by subscript may not be needed. Your code can still fail if there's only one item and you try to access items[1].

Instead you can probably use first to access the first element:

ZStack {
if fetch.loading {
LoadingView()
} else if let item = fetch.items.first {
Text(item.name)
.bold()
} else {
Text("Items are empty")
}
}

or use a ForEach to display all the items:

ZStack {
if fetch.loading {
LoadingView()
} else if fetch.items.isEmpty {
Text("Items are empty")
} else {
VStack {
ForEach(fetch.items, id: \.name) { item in
Text(item.name)
.bold()
}
}
}
}

Also, if possible, avoid force unwrapping optionals. The code snapshot!.documents will terminate your app if snapshot == nil. Many useful solutions are presented in this answer:

  • What does “Fatal error: Unexpectedly found nil while unwrapping an Optional value” mean?


Related Topics



Leave a reply



Submit