1

I am trying to dynamically create sections for List with a header in SwiftUI.

here is my array:

 var lists = [a list of names with A to Z] // array of strings

then I try to get first letter:

var firstCharacters: [Character] {
        var firstCharacters = [Character]()
        for list in lists.sorted(by: {$0 < $1}) {
            if let character = list.first, !firstCharacters.contains(character) {
                firstCharacters.append(character)
            }
        }
        return firstCharacters
    }

I created the list like this:

List {
    ForEach(firstCharacters, id: \.self) { charachter in
        Section(header: Text("\(charachter.description)")) {
        ForEach(Array(lists.enumerated()), id:\.element) {  index, element in
            Text("Name \(element), Id: \(index)")

                })
            }
        }
    }
}

Now I have a problem adding section to the list now sure how can I combine with the list.

Mc.Lover
  • 4,813
  • 9
  • 46
  • 80

3 Answers3

2

A better way to section this is to take your list of words and turn it into a dictionary keyed by the first letter like this:

var wordDict: [String:[String]] {
    let letters = Set(lists.compactMap( { $0.first } ))
    var dict: [String:[String]] = [:]
    for letter in letters {
        dict[String(letter)] = lists.filter( { $0.first == letter } ).sorted()
    }
    return dict
}

Then use the Dict in your List like this:

    List {
        // You then make an array of keys, sorted by lowest to highest
        ForEach(Array(wordDict.keys).sorted(by: <), id: \.self) { character in
            Section(header: Text("\(character)")) {
                // Because a dictionary lookup can return nil, we need to provide a response
                // if it fails. I used [""], though I could have just force unwrapped.
                ForEach(wordDict[character] ?? [""], id: \.self) { word in
                    Text("Name \(word)")
                }
            }
        }
    }

This prevents you from having to iterate through the whole list for every letter. It is done once when creating the Dict. You no longer need var firstChar.

Yrb
  • 8,103
  • 2
  • 14
  • 44
  • Do you mean the original indices in List? They are long gone in this. If you need to keep track of IDs associated with the words in the list, your better bet is to use a struct that has both the word and id as parameters. When using FOrEach, you really want to stay away from Types that are not `Identifiable`. This code is example, not production. – Yrb Jun 01 '22 at 18:26
1

The following would print the names in each section:

struct ContactsView: View {
    let names = ["James", "Steve", "Anna", "Baxter", "Greg", "Zendy", "Astro", "Jenny"]

    var firstChar: [Character] {
        let array =  names
            .compactMap({ $0.first })
                    
        // Set is just a quick way to remove duplicates.
        return Array(Set(array))
            .sorted(by: { $0 < $1 })
    }

    var body: some View {
        List {
            ForEach(firstChar, id: \.self) { char in
                Section(header: Text("\(char.description)")) {
                    ForEach(names, id: \.self) { name in
                        if name.first == char {
                            Text(name)
                        }
                    }
                }
            }
        }
    }
}

Although a better way might be to not have 2 different arrays but merge them into one where the first letter is the key and the values are the names.

That way you don't have to iterate over every name for every section.

Olle Ekberg
  • 804
  • 8
  • 11
0

My recommendation is a view model which groups an array of strings into a Section struct with Dictionary(grouping:by:)

class ViewModel : ObservableObject {
    struct Section: Identifiable {
        let letter : String
        let names : [String]
    
        var id : String { letter }
    }
    
    @Published var sections = [Section]()
    
    var names = [String]() {
        didSet {
            let grouped = Dictionary(grouping: names, by: {$0.prefix(1)})
            sections = grouped.keys.sorted().map{Section(letter: String($0), names: grouped[$0]!)}
        }
    }
}

Whenever the content of the array is being modified the section array (and the view) is updated.


In the view in onAppear pass some names

struct ContentView: View {
    @StateObject private var model = ViewModel()

    var body: some View {
        
        List(model.sections) { section in
            Section(section.letter) {
                ForEach(section.names, id: \.self, content: Text.init)
            }
        }
        .onAppear {
            model.names = ["Aaran", "Aaren", "Aarez", "Badsha", "Bailee", "Bailey", "Bailie", "Bailley", "Carlos", "Carrich", "Carrick", "Carson", "Carter", "Carwyn", "Dante", "Danyal", "Danyil", "Danys", "Daood", "Dara", "Darach", "Daragh", "Darcy", "D'arcy", "Dareh", "Eisa", "Eli", "Elias", "Elijah", "Eliot", "Elisau", "Finn", "Finnan", "Finnean", "Finnen", "Finnlay", "Geoff", "Geoffrey", "Geomer", "Geordan", "Hamad", "Hamid", "Hamish", "Hamza", "Hamzah", "Han", "Idris", "Iestyn", "Ieuan", "Igor", "Ihtisham", "Jarno", "Jarred", "Jarvi", "Jasey-Jay", "Jasim", "Jaskaran", "Jason", "Jasper", "Jaxon", "Kabeer", "Kabir", "Kacey", "Kacper", "Kade", "Kaden", "Kadin", "Kadyn", "Kaeden", "Kael", "Kaelan", "Kaelin", "Kaelum", "Kai", "Kaid", "Kaidan", "Kaiden", "Kaidinn", "Kaidyn", "Kaileb", "Kailin", "Karsyn", "Karthikeya", "Kasey", "Kash", "Kashif", "Kasim", "Kasper", "Kasra", "Kavin", "Kayam", "Leiten", "Leithen", "Leland", "Lenin", "Lennan", "Lennen", "Lennex", "Lennon", "Lennox", "Lenny", "Leno", "Lenon", "Lenyn", "Leo", "Leon", "Leonard", "Leonardas", "Leonardo", "Lepeng", "Leroy", "Leven", "Levi", "Levon", "Machlan", "Maciej", "Mack", "Mackenzie", "Mackenzy", "Mackie", "Macsen", "Macy", "Madaki", "Nickson", "Nicky", "Nico", "Nicodemus", "Nicol", "Nicolae", "Nicolas", "Nidhish", "Nihaal", "Nihal", "Nikash", "Olaoluwapolorimi", "Ole", "Olie", "Oliver", "Olivier", "Peter", "Phani", "Philip", "Philippos", "Phinehas", "Phoenix", "Phoevos", "Pierce", "Pierre-Antoine", "Pieter", "Pietro", "Piotr", "Porter", "Prabhjoit", "Prabodhan", "Praise", "Pranav", "Rasul", "Raul", "Raunaq", "Ravin", "Ray", "Rayaan", "Rayan", "Rayane", "Rayden", "Rayhan", "Santiago", "Santino", "Satveer", "Saul", "Saunders", "Savin", "Sayad", "Sayeed", "Sayf", "Scot", "Scott", "Scott-Alexander", "Seaan", "Seamas", "Seamus", "Sean", "Seane", "Sean-James", "Sean-Paul", "Sean-Ray", "Seb", "Sebastian", "Sebastien", "Selasi", "Seonaidh", "Sephiroth", "Sergei", "Sergio", "Seth", "Sethu", "Seumas", "Shaarvin", "Shadow", "Shae", "Shahmir", "Shai", "Shane", "Shannon", "Sharland", "Sharoz", "Shaughn", "Shaun", "Tadhg", "Taegan", "Taegen", "Tai", "Tait", "Uilleam", "Umair", "Umar", "Umer", "Umut", "Urban", "Uri", "Usman", "Uzair", "Uzayr", "Valen", "Valentin", "Valentino", "Valery", "Valo", "Vasyl", "Vedantsinh", "Veeran", "Victor", "Victory", "Vinay", "Vince", "Wen", "Wesley", "Wesley-Scott", "Wiktor", "Wilkie", "Will", "William", "William-John", "Willum", "Wilson", "Windsor", "Wojciech", "Woyenbrakemi", "Wyatt", "Wylie", "Wynn", "Xabier", "Xander", "Xavier", "Xiao", "Xida", "Xin", "Xue", "Yadgor", "Yago", "Yahya", "Yakup", "Yang", "Yanick", "Yann", "Yannick", "Yaseen", "Yasin", "Yasir", "Yassin", "Yoji", "Yong", "Yoolgeun", "Yorgos", "Youcef", "Yousif", "Youssef", "Yu", "Yuanyu", "Yuri", "Yusef", "Yusuf", "Yves", "Zaaine", "Zaak", "Zac", "Zach", "Zachariah", "Zacharias", "Ziyaan", "Zohaib", "Zohair", "Zoubaeir", "Zubair", "Zubayr", "Zuriel"]
        }
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361