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
Arkit - How to Export Obj from Iphone/iPad with Lidar
Property Observers Willset and Didset; Property Getters and Setters
How Does One Trap Arithmetic Overflow Errors in Swift
Trying to Know When a Window Closes in a MACos Document Based Application
Swift Watchos 2 - Cmsensordatalist
Firebase Query Ordering Not Working Properly
Swift - Could Not Cast Value of Type '_Nscfstring' to 'Nsdictionary'
Please Help Me Intepret This Code Swift
Read-Only Properties of Protocols in Swift
What Are the Supported Swift String Format Specifiers
Change Color Searchbar Result Icon Swift
Swift Turn a Country Code into a Emoji Flag via Unicode
How to Use Array.Filter to Filter a Class Object Based on a Property
Is Swift Dictionary of Indexed for Performance? Even for Exotic Types (Uuid)
What Are "Intervals" in Swift Ranges
How to Do "Deep Copy" in Swift
Swift Protocol Error: 'Weak' Cannot Be Applied to Non-Class Type