skip to Main Content

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.

3

Answers


  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.

    Login or Signup to reply.
  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.

    Login or Signup to reply.
  3. 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"]
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search