Home > Mobile >  Dynamically create section for list in SwiftUI
Dynamically create section for list in SwiftUI

Time:06-02

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.

CodePudding user response:

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.

CodePudding user response:

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.

CodePudding user response:

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"]
        }
    }
}
  • Related