How use use SwiftUI ForEach with a dictionary [ customEnum : customStrut ] - getting conform to 'RandomAccessCollection' error
As states dictionary is not "random access" capable collection, so it cannot be used directly in ForEach, here is possible approach
HStack {
ForEach(Array(filterSelections.keys.enumerated()), id:\.element) { _, key in
Text("TBD \(self.filterSelections[key]?.title ?? "")")
// Will be putting checkboxes here - i.e. so can chose which ones
}
}
Is it possible to iterate a dictionary inside ForEach in SwiftUI?
This is because a Dictionary is unordered. So its keys are unordered as well.
You need to make it ordered using Array
:
ForEach(Array(viewModel.userDomains.keys).indices, id: \.self) { ...
As they are ordered randomly, you may also want to sort them:
ForEach(Array(viewModel.userDomains.keys).sorted().indices, id: \.self) { ...
But as the ordering and sorting gets more complex, you may think of moving the logic to the ViewModel
instead.
[SwiftUI]: ForEach is not working for array of dictionary, dictionary with array
You can write it cleanly with
struct Model:Hashable {
let type:String
let unnits:[String]
}
struct ContentView: View {
let arr_type2 = [Model(type:"Temperature", unnits: ["°C","°F","°K"])]
var body: some View {
ForEach(arr_type2, id: \.self) { item in
Text(item.type)
}
}
}
For why check https://www.hackingwithswift.com/books/ios-swiftui/why-does-self-work-for-foreach
MapKit error when using AnnotatedItem in a ForEach
ForEach
is for use inside a View
hierarchy. You may be thinking of forEach
, which can be used to perform loops on array elements, but it appears that what you actually want in this case is .map
, which allows you to transform one array into another:
private var pointsOfInterest : [AnnotatedItem] {
countries.map { country in
AnnotatedItem(name: country.display_name, coordinate: .init(latitude: country.latitude, longitude: country.longitude))
}
}
Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Planet' conform to 'Identifiable'
ForEach
needs to identify its contents in order to perform layout, successfully delegate gestures to child views and other tasks. Identifying the content means that there has to be some variable (which itself needs to conform to Hashable
) that ForEach
can use. In most cases, this is as simple as making your struct (Planet
) conform to the Identifiable
protocol. You did not provide implementation details of your Planet
s struct, but here is one way I could hink about that displays the point:
struct Planet: Identifiable {
var id: String {
self.name
}
var name: String
}
In this example, I'm assuming that the nam of a planet uniquely identifies it. Common ways of achieving this task are
- Using
UUID
as identifier - Assigning a different integer (or using a static factory) to each new instance
- Using an otherwise
Hashable
unique variable.
If you don't want to make your struct conform to Identifiable
, you can also provide the variable you want this to be identified as as a key-path:
struct PlanetView: View {
let planets: [Planet]
var body: some View {
ForEach(planets, id: \.name) { planet in
//...
}
}
}
ForEach & indexof SwiftUI keep giving Closure containing control flow statement cannot be used with result builder 'TableRowBuilder'
ForEach
is a View
it isn't a for loop and you can't use \.self
or use array.count
, you have to supply a key that is a unique identifier for the data, or make your data struct implement Identifiable
and have a let id
set it from the JSON data.
How do you use .enumerated() with ForEach in SwiftUI?
TL;DR
Warning: If you get in the habit of using enumerated()
with ForEach
, you may one day end up with EXC_BAD_INSTRUCTION
or Fatal error: Index out of bounds
exceptions. This is because not all collections have 0-based indexes.
A better default is to use zip
instead:
ForEach(Array(zip(items.indices, items)), id: \.0) { index, item in
// index and item are both safe to use here
}
The folks over at Point-Free mentioned that it's not safe to rely on enumerated()
with ForEach
in production since not all collections are zero-index based:
This is technically not the most correct way to do this. It would be more correct, and more verbose, to zip the
todos
array with its indices collection. In this case we are safe because we are dealing with a simple 0-based index array, but if we were doing this in production we should probablyzip
-based approach.
Apple's documentation for the enumerated function mentions this as well:
/// Returns a sequence of pairs (*n*, *x*), where *n* represents a
/// consecutive integer starting at zero and *x* represents an element of
/// the sequence.
///
/// This example enumerates the characters of the string "Swift" and prints
/// each character along with its place in the string.
///
/// for (n, c) in "Swift".enumerated() {
/// print("\(n): '\(c)'")
/// }
/// // Prints "0: 'S'"
/// // Prints "1: 'w'"
/// // Prints "2: 'i'"
/// // Prints "3: 'f'"
/// // Prints "4: 't'"
///
/// When you enumerate a collection, the integer part of each pair is a counter
/// for the enumeration, but is not necessarily the index of the paired value.
/// These counters can be used as indices only in instances of zero-based,
/// integer-indexed collections, such as `Array` and `ContiguousArray`. For
/// other collections the counters may be out of range or of the wrong type
/// to use as an index. To iterate over the elements of a collection with its
/// indices, use the `zip(_:_:)` function.
///
/// This example iterates over the indices and elements of a set, building a
/// list consisting of indices of names with five or fewer letters.
///
/// let names: Set = ["Sofia", "Camilla", "Martina", "Mateo", "Nicolás"]
/// var shorterIndices: [Set<String>.Index] = []
/// for (i, name) in zip(names.indices, names) {
/// if name.count <= 5 {
/// shorterIndices.append(i)
/// }
/// }
///
/// Now that the `shorterIndices` array holds the indices of the shorter
/// names in the `names` set, you can use those indices to access elements in
/// the set.
///
/// for i in shorterIndices {
/// print(names[i])
/// }
/// // Prints "Sofia"
/// // Prints "Mateo"
///
/// - Returns: A sequence of pairs enumerating the sequence.
///
/// - Complexity: O(1)
In your specific case enumerated()
is fine to use since you are using a 0-based index array, however due to the details above, relying on enumerated()
all the time can lead to non-obvious errors.
Take this snippet, for example:
ForEach(Array(items.enumerated()), id: \.offset) { offset, item in
Button(item, action: { store.didTapItem(at: offset) })
}
// ...
class Store {
var items: ArraySlice<String>
func didTapItem(at index: Int) {
print(items[index])
}
}
First notice that we dodged a bullet with Button(item...
since enumerated()
has guaranteed that item
can be accessed directly without causing an exception. However, if instead of item
we used items[offset]
, an exception could easily be raised.
Finally, the line print(items[index])
can easily lead to an exception since the index (really the offset) can be out of bounds.
Therefore, a safer approach is to always use the zip
method mentioned at the top of this post.
Another reason to prefer zip
is that if you tried using the same code with a different Collection (e.g. Set) you could get the following syntax error when indexing into the type (items[index]
):
Cannot convert value of type 'Int' to expected argument type 'Set.Index'
By using the zip
based approach, you can still index into the collection.
You could also create an extension on collection if you plan on using it often.
You can test this all out in a Playground:
import PlaygroundSupport
import SwiftUI
// MARK: - Array
let array = ["a", "b", "c"]
Array(array.enumerated()) // [(offset 0, element "a"), (offset 1, element "b"), (offset 2, element "c")]
Array(zip(array.indices, array)) // [(.0 0, .1 "a"), (.0 1, .1 "b"), (.0 2, .1 "c")]
let arrayView = Group {
ForEach(Array(array.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
Text("value: \(array[offset])")
}
// offset: 0, element: a
// offset: 1, element: b
// offset: 2, element: c
ForEach(Array(zip(array.indices, array)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(array[index])")
}
// index: 0, element: a
// index: 1, element: b
// index: 2, element: c
}
// MARK: - Array Slice
let arraySlice = array[1...2] // ["b", "c"]
Array(arraySlice.enumerated()) // [(offset 0, element "b"), (offset 1, element "c")]
Array(zip(arraySlice.indices, arraySlice)) // [(.0 1, .1 "b"), (.0 2, .1 "c")]
// arraySlice[0] // ❌ EXC_BAD_INSTRUCTION
arraySlice[1] // "b"
arraySlice[2] // "c"
let arraySliceView = Group {
ForEach(Array(arraySlice.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
// Text("value: \(arraySlice[offset])") ❌ Fatal error: Index out of bounds
}
// offset: 0, element: b
// offset: 1, element: c
ForEach(Array(zip(arraySlice.indices, arraySlice)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(arraySlice[index])")
}
// index: 1, element: b
// index: 2, element: c
}
// MARK: - Set
let set: Set = ["a", "b", "c"]
Array(set.enumerated()) // [(offset 0, element "b"), (offset 1, element "c"), (offset 2, element "a")]
Array(zip(set.indices, set)) // [({…}, .1 "a"), ({…}, .1 "b"), ({…}, .1 "c")]
let setView = Group {
ForEach(Array(set.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
// Text("value: \(set[offset])") // ❌ Syntax error: Cannot convert value of type 'Int' to expected argument type 'Set<String>.Index'
}
// offset: 0, element: a
// offset: 1, element: b
// offset: 2, element: c
ForEach(Array(zip(set.indices, set)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(set[index])")
}
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 0), age: -481854246))), element: a
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 2), age: -481854246))), element: b
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 3), age: -481854246))), element: c
}
// MARK: -
struct PrintView: View {
init(_ string: String) {
print(string)
self.string = string
}
var string: String
var body: some View {
Text(string)
}
}
let allViews = Group {
arrayView
arraySliceView
setView
}
PlaygroundPage.current.setLiveView(allViews)
Updates:
- Deleted the part that mentioned you can use
\.1
since Peacemoon points out this could cause problems. Also I'm pretty sure if your items conform toIdentifiable
, there's no point in using zip in the first place, you should be able to just doForEach(identifiableItems)
.
SwiftUI - Automatically add dividers between each element of a `ForEach`
Does everything need to be in a ForEach? If not, you can consider not using indices at all:
struct ForEachDividerView<Data, Content, ID>: View where Data: RandomAccessCollection, ID: Hashable, Content: View {
var data: Data
var id: KeyPath<Data.Element, ID>
var content: (Data.Element) -> Content
var body: some View {
if let first = data.first {
content(first)
ForEach(data.dropFirst(), id: id) { element in
Divider()
.background(.blue)
content(element)
}
}
}
}
NavigationStack Type '() - ()' cannot conform to 'View'
You have added extra {
after route in
and because of this Xcode
added }
as well. So remove this extra start {
and end }
. Now after fixing this in your switch case you missed handling of app
case because of this you will get Switch must be exhaustive
error. To fix this add app
case or default
case as well in your switch. After fixing both issues your code will look like this.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
.navigationDestination(for: Route.self) { route in
switch route {
case .login:
Text("Login")
case .signup:
Text("Sign Up")
case .app:
Text("App")
}
}
}
}
}
}
Related Topics
Parse Migration Error to Mlabs and Heroku
Why Does a Simple Swift Arithmetic Operation Compile So Slow
Cannot Use Mutating Member ... Because Append
How to Check Dictionary Value Is Empty or Not
Check If Avaudioplayer Is Playing
Index or Range of Second Ocurence of Bytes in File
Spritekit How to Create and Transition to Different Scenes (Swift Language)
Can't Set @Ibinspectable Computed Property in UIview
Swiftui Map Overlays Without UIviewrepresentable
User Already Authenticated in App and Firebase Realtime Database Returns Failed: Permission_Denied
Loading Image from Remote Url Asynchronously in Swiftui Image Using Combine's Publisher
Filter, Closure, Functional Syntax Version of for Loop with Multiple Conditions
Changing a Label in Prepareforsegue
How to Send Multiple Buttons in Button.Addtarget Action? Swift3