This question already has answers here:
SwiftUI iterating through dictionary with ForEach
(10 answers)
Closed 1 year ago.
I am trying to make a list with a dictionary. My dictionary is located in a model. My dictionary is [String : String]. I tried to sort it hopefully it is sorted by alphabetical. I couldn't figure it out why it does not work
var fw: Deck
var body: some View {
let sortedDict = fw.dictItems.sorted(by: < )
let keys = sortedDict.map {$0.key}
let values = sortedDict.map {$0.value}
return List{
ForEach(keys.indices) { index in
HStack {
Text(keys[index])
Text("\(values[index])")
}
}
}
}
}
Here is a way for you:
struct ContentView: View {
#State var entries: [String: String] = ["key1":"val1", "key2":"val2", "key3":"val3", "key4":"val4"]
var body: some View {
List {
ForEach(entries.keys.sorted(by: <), id: \.self) { key in
HStack {
Text(key)
Text(entries[key]!)
}
}
}
}
}
Related
I have a Data class as follows:
class Data: Hashable, Equatable {
var id: Int = 0
var name: String = ""
var date: Date = Date()
var paymentStatus: Bool = false
static func == (lhs: Data, rhs: Data) -> Bool {
return lhs.id ==
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
var data1: Data {
let data = Data()
data.id = 1
data.name = "John"
data.date = Calendar.current.date(byAdding: DateComponents(day: -6), to: Date())!
data.paymentStatus = true
return data
}
var data2: Data {
let data = Data()
data.id = 2
data.name = "Peter"
data.date = Date()
data.paymentStatus = false
return data
}
I’m trying to display the data in sections as follows:
struct ContentView: View {
var data: [Data] = [data1, data2]
var body: some View {
List {
ForEach(groupByDate(data), id: \.self) { studentsInMonth in
Section(header:Text(Date(), style: .date)) {
ForEach(data, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
}
}
func groupByDate(_ data: [Data]) -> [Date: [Data]] {
let empty: [Date: [Data]] = [:]
return data.reduce(into: empty) { acc, cur in
let components = Calendar.current.dateComponents([.year, .month], from: cur.date)
let date = Calendar.current.date(from: components)!
let existing = acc[date] ?? []
acc[date] = existing + [cur]
}
}
Not sure what mistake I’m making, but its throwing two errors:
Cannot convert value of type ‘[Date:[Data]]’ to expected argument type
Generic parameter ‘C’ could not be inferred
Appreciate any help
Right now, you're trying to iterate through a Dictionary using ForEach, which doesn't work -- ForEach expects a RandomAccessCollection, which is most often, an Array.
Instead, you can iterate over just the keys of the Dictionary.
struct ContentView: View {
var data: [Data] = [data1, data2]
var body: some View {
let grouped = groupByDate(data)
List {
ForEach(Array(grouped.keys), id: \.self) { key in
let studentsInMonth = grouped[key]!
Section(header:Text(key, style: .date)) {
ForEach(studentsInMonth, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
}
}
Although the above works, I'd consider refactoring your groupByDate function so that it returns an Array directly instead of having to go through the above transformations.
Also, unrelated to your question, but right now, in your header, you're displaying a date -- you get just the year and month, but then display it as a full date, so the header says "January 1, 2022" for all the January dates. You probably want to refactor this to just show the month and year.
I have data in a dictionary which is [String: String]. What I want to provide is an interface to the user to edit the values in the dictionary, while the keys remain fixed. I can see how to display the values, but putting them into a TextField is what I want, and haven't been able to find how to do.
Here is the code:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.sorted(by: <), id: \.key) { key, value in
HStack {
Text(key)
TextField("", text: $entries[key])
}
}
}
}
}
This doesn't compile, with no fewer than three errors on the TextField line:
Cannot convert value of type 'Slice<Binding<[String : String]>>' to expected argument type 'Binding'
Cannot convert value of type 'String' to expected argument type 'Range<Binding<[String : String]>.Index>'
Referencing subscript 'subscript(_:)' on 'Binding' requires that '[String : String]' conform to 'MutableCollection'
So obviously I am doing things incorrectly, but I am lost trying to find what the correct way would be, and haven't been able to find an answer in an internet search. Can anyone point me in the right direction?
you could try this simple approach:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.keys.sorted(by: <), id: \.self) { key in
HStack {
Text(key)
TextField("", text: Binding(
get: { entries[key]! },
set: { entries[key] = $0 }
))
}
}
}
}
}
struct ContentView: View {
#State var entries: [String: String] = ["key1":"val1", "key2":"val2", "key3":"val3", "key4":"val4"]
var body: some View {
dictionaryEditor(entries: $entries)
Button(action: { print("----> entries: \(entries)") }) {
Text("print entries")
}
}
}
The problem is that entries[key] returns an optional String value while the text parameter of TextField expects a Binding of non optional String.
You can create an optional binding extension and then you can use it safely:
extension Binding where Value == String? {
var optionalBind: Binding<String> {
.init(
get: {
wrappedValue ?? ""
}, set: {
wrappedValue = $0
}
)
}
}
Then you can just add the optionalBind to your code:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.sorted(by: <), id: \.key) { key, value in
HStack {
Text(key)
TextField("", text: $entries[key].optionalBind) // <--
}
}
}
}
}
This question already has answers here:
Why can't I store a value and a reference to that value in the same struct?
(4 answers)
How do I create a BinaryHeap that pops the smallest value, not the largest?
(2 answers)
Take element with lowest value from a HashSet?
(1 answer)
How to get the minimum value within a vector in Rust?
(3 answers)
What is the idiomatic way to get the index of a maximum or minimum floating point value in a slice or Vec in Rust?
(5 answers)
Closed 1 year ago.
I have a toy app where I can add entries to a collection, but I cannot modify the add method to keep track of the smallest entry.
The basic version is here
I think I need to add something like a smallest_entry: Option<&'a Entry> to the struct that holds the entries, but I cannot figure out how to modify the add_entry
the data structure is
struct Entry {
pub value: usize,
}
struct Source {
pub entries: Vec<Entry>,
// pub smallest_entry: Option<&'a Entry>,
}
struct SourcesRepository {
pub map: HashMap<String, Source>,
}
and the current add_entry method is
impl SourcesRepository {
fn add_entry(&mut self, title: String, entry: Entry) {
match self.map.get_mut(&title) {
Some(source) => {
// we already have a Source for this title: add new entry to it
source.entries.push(entry);
}
None => {
// no source existing for this title: create new and add entry
let mut source = Source {
entries: Vec::new(),
};
source.entries.push(entry);
self.map.insert(title, source);
}
}
}
}
I imagine once I have the source entry I should compare it with the smallest_entry and update that reference if the new entry is smaller. I've tried to modify the code but I cannot make the lifetimes work.
impl SourcesRepository<'_> {
fn add_entry(&mut self, title: String, entry: Entry) {
match self.map.get_mut(&title) {
Some(source) => {
// we already have a Source for this title: add new entry to it
match source.smallest_entry {
Some(smallest_entry) => {
if entry.value < smallest_entry.value {
source.smallest_entry = Some(&entry);
}
}
None => {
source.smallest_entry = Some(&entry);
}
}
source.entries.push(entry);
}
None => {
// no source existing for this title: create new and add entry
let mut source = Source {
entries: Vec::new(),
smallest_entry: Some(&entry),
};
source.entries.push(entry);
self.map.insert(title, source);
}
}
}
}
link to broken version
Ok, I'll preface this with the fact that I'm new to SwiftUI and programming. I'm a UX Designer. However, I'm trying to run a simple Firestore query and return the results to a list.
I've been able to write a function that writes the results to the console successfully, but I have no idea how to access the information that's within the function so that I can use it within the main view of the page.
I've started a simple view so that I can just focus on displaying Firestore data in a list. Here's my barebones code currently.
import SwiftUI
import FirebaseFirestore
struct FestivalListFB: View {
let db = Firestore.firestore()
func getVenues() {
let db = Firestore.firestore()
db.collectionGroup("Venues").getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
guard let venueEntry = document.get("venueTitle") as? String else {
continue
}
print(venueEntry)
}
}
}
}
var body: some View {
VStack {
List(0 ..<5) { item in
Text("Hello, World!")
}
}.onAppear(perform: getVenues)
}
}
Console displays:
"Refreshment Outpost
Holiday Sweets & Treats
Fife & Drum Tavern
L'Artisan des Glaces
Shi Wasu
...etc"
And of course, the body only displays "Hello World" 5 times within a list. How would I go about accessing the values in "venueEntry" so that I can display it within the list element?
I've included a image of my Firestore data structure as well. Ideally, I'd like to display the venues grouped by the "venueArea" they are located in.
For easier use, i created a model for you venue. See the below snippet how you can show your data in your View.
Your model:
class VenueObject: ObservableObject {
#Published var venueID: String
#Published var venueTitle: String
#Published var venueArea: String
init(id: String, title: String, area: String) {
venueID = id
venueTitle = title
venueArea = area
}
}
Your View:
struct FestivalListFB: View {
#State var data: [VenueObject] = []
let db = Firestore.firestore()
var body: some View {
VStack {
ForEach((self.data), id: \.self.venueID) { item in
Text("\(item.venueTitle)")
}
}.onAppear {
self.getVenues()
}
}
func getVenues() {
// Remove previously data to prevent duplicate data
self.data.removeAll()
self.db.collectionGroup("Venues").getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let title = document.get("venueTitle") as! String
let area = document.get("venueArea") as! String
self.data.append(VenueObject(id: id, title: title, area: area))
}
}
}
}
}
I have an array of dictionaries I would like to populate in a list view with SwiftUI.
I used for loops in the past but since that's not possible within a View I'm stuck as to what to do. I'm able to achieve partial results with the code below.
struct Test : View {
let dict = csvArray[0]
var body: some View {
let keys = dict.map{$0.key}
let values = dict.map {$0.value}
return List {
ForEach(keys.indices) {index in
HStack {
Text(keys[index])
Text("\(values[index])")
}
}
}
}
}
I'm looking to index through the entire Array of dictionaries and append them to the list, not just csvArray[0].
This is like Sections of key values, right?
So do it like:
This is an example of csvArray, This could be anything but you should update the rest of the code duo to the original data type
let csvArray = [
[ "section0-key0": "section0-value0",
"section0-key1": "section0-value1"],
[ "section1-key0": "section1-value0",
"section1-key1": "section1-value1"],
[ "section2-key0": "section2-value0",
"section2-key1": "section2-value1"]
]
This is your code for a single dictionary. but this will take that dictionary instead of hardcoded one:
struct SectionView : View {
#State var dict = [String: String]()
var body: some View {
let keys = dict.map{$0.key}
let values = dict.map {$0.value}
return ForEach(keys.indices) {index in
HStack {
Text(keys[index])
Text("\(values[index])")
}
}
}
}
And this is the list builder connected to the original array. I used sections for the meaningfulness of the data structure.
struct ContentView: View {
var body: some View {
List {
ForEach(csvArray, id:\.self) { dict in
Section {
SectionView(dict: dict)
}
}
}
}
}
Note that you can't relay on the order of the key value in a dictionary. So I suggest you to do some sorting before populating the list or use another data structure like class or struct instead of a plain dictionary.