Search code examples
swiftuikit

Wanna do indexed table view sorting by user's surname using data model, but cannot understand how


I'm new to this, so this question might be stupid. I have made some indexed table view with user names. Now I added a label with user surnames and I want to make my indexed table view sorted by user's surnames using data model and I just really have no idea how to do that. It may be helpful, so here you can watch how my app is working right now. The first, there are FriendsSearchViewController with all users template.

import UIKit

final class FriendsSearchViewController: UITableViewController {

    var friends = [
        "Polina",
        "Ivan",
        "Pavel",
        "Maria",
        "Nick"
    ]
    
    var userFriends: [String] = []
    var friendSectionTitles = [String]()
    var friendsDictionary = [String: [String]]()

    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(
            nibName: "FriendCell",
            bundle: nil),
                           forCellReuseIdentifier: "friendCell")
        
        for friend in friends {
            let friendKey = String(friend.prefix(1))
            if var friendValues = friendsDictionary[friendKey] {
                friendValues.append(friend)
                friendsDictionary[friendKey] = friendValues
            } else {
                friendsDictionary[friendKey] = [friend]
            }
        }
        
        friendSectionTitles = [String](friendsDictionary.keys)
        friendSectionTitles = friendSectionTitles.sorted(by: { $0 < $1 })
    }
    
    // MARK: - Table view data source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        friendSectionTitles.count
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let friendKey = friendSectionTitles[section]
        if let friendValues = friendsDictionary[friendKey] {
            return friendValues.count
        }
        
        return 0
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        friendSectionTitles[section]
    }
    
    override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        friendSectionTitles
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: "friendCell", for: indexPath) as? FriendCell
        else { return UITableViewCell() }
        
        var currentFriend = friends[indexPath.row]
        
        let friendKey = friendSectionTitles[indexPath.section]
        if let friendValues = friendsDictionary[friendKey] {
            currentFriend = friendValues[indexPath.row]
        }

        cell.configure(
            photo: UIImage(named: "\(indexPath.row)") ?? UIImage(),
            name: currentFriend,
            surname: "")

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        defer {
            tableView.deselectRow(at: indexPath, animated: true)
        }
        
        let friendKey = friendSectionTitles[indexPath.section]
        var currentFriend = ""
        if let friendValues = friendsDictionary[friendKey] {
            currentFriend = friendValues[indexPath.row]
        }
        
        if userFriends.firstIndex(of: currentFriend) == nil {
            userFriends.append(currentFriend)
        }
        
        self.performSegue(withIdentifier: "addFriend", sender: nil)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "addFriend",
               let myFriendsViewController = segue.destination as? MyFriendsViewController {
                myFriendsViewController.friends = userFriends
        }
    }
}

And there are MyFriendsViewController with users that have been added to friends list:

import UIKit

final class MyFriendsViewController: UITableViewController {
    var friends = [String]() {
        didSet {
            //
        }
    }
    
    
    @IBAction func addFriend(segue: UIStoryboardSegue) {
        guard segue.identifier == "addFriend",
            let allFriendsViewController = segue.source as? FriendsSearchViewController
        else { return }
        friends = allFriendsViewController.userFriends
        tableView.reloadData()
    }
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(
            nibName: "FriendCell",
            bundle: nil),
                           forCellReuseIdentifier: "friendCell")
    }
    
    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        friends.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: "friendCell", for: indexPath) as? FriendCell
        else { return UITableViewCell() }
        
        let currentFriend = friends[indexPath.row]

        cell.configure(
            photo: UIImage(named: "\(indexPath.row)") ?? UIImage(),
            name: currentFriend,
            surname: "")

        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        defer { tableView.deselectRow(
            at: indexPath,
            animated: true)}
        performSegue(
            withIdentifier: "showProfile",
            sender: nil)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "addFriend",
           let allFriendsViewController = segue.destination as? FriendsSearchViewController {
            allFriendsViewController.userFriends = friends
        }
    }
}

Also there are UserModel that probably looks not correctly:

import UIKit

struct UserModel {
    let userName: String
    let userSurname: String
    let userPhoto: UIImage
    let userAge: String
}

And FriendCell with cell configuration:

import UIKit

class FriendCell: UITableViewCell {
    @IBOutlet var friendPhoto: AvatarImage!
    @IBOutlet var friendName: UILabel!
    @IBOutlet var friendSurname: UILabel!
    
    func configure(
        photo: UIImage,
        name: String,
        surname: String) {
            self.friendPhoto.image = photo
            self.friendName.text = name
            self.friendSurname.text = surname
        }
}

I'm just cannot imagine what should I do. How do I should made it? Please, can you give me some ideas with code examples? Thank you!


Solution

  • 1.

    Make some small adjustments to the UserModel

    // Make UserModel conform to Equatable protocol so you can
    // compare two UserModel Objects using == or firstIndexOf
    // Read more on Equatable here: https://developer.apple.com/documentation/swift/equatable
    struct UserModel: Equatable {
        // I changed this to userFirstName to be more clear
        let userFirstName: String
        
        let userSurname: String
        
        // I made UIImage? optional, but you can change if you wish
        let userPhoto: UIImage?
        
        let userAge: Int
    }
    

    2.

    Add struct to array instead of strings in FriendsSearchViewController

    // Update friends array to hold structs of people
    var friends = [
        UserModel(userFirstName: "Polina",
                  userSurname: "James",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20),
        UserModel(userFirstName: "Ivan",
                  userSurname: "Gomez",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20),
        UserModel(userFirstName: "Pavel",
                  userSurname: "Harvey",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20),
        UserModel(userFirstName: "Maria",
                  userSurname: "Fernando",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20),
        UserModel(userFirstName: "Nick",
                  userSurname: "Cage",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20),
        UserModel(userFirstName: "Shawn",
                  userSurname: "Frank",
                  userPhoto: UIImage(systemName: "star"),
                  userAge: 20)
    ]
    

    3.

    Change these to be struct arrays, not String in FriendsSearchViewController

    // userFriends should now be an array of UserModel type, not String
    var userFriends: [UserModel] = []
    var friendSectionTitles = [String]()
    
    // Also friendsDictionary should be an array of UserModel type, not String
    var friendsDictionary = [String: [UserModel]]()
    

    4.

    Adjust loop in viewDidLoad to work with Structs as they will not be strings anymore in FriendsSearchViewController

    // Now friend will be of type UserModel, not string
    for friend in friends {
        
        // So we need to check the prefix of the UserMode.userSurname
        let friendKey = String(friend.userSurname.prefix(1))
        
        if var friendValues = friendsDictionary[friendKey] {
            friendValues.append(friend)
            friendsDictionary[friendKey] = friendValues
        } else {
            friendsDictionary[friendKey] = [friend]
        }
    }
    
    friendSectionTitles = [String](friendsDictionary.keys)
    friendSectionTitles = friendSectionTitles.sorted(by: { $0 < $1 })
    

    5. Adjust tableView datasource functions to work with structs instead of strings

    Fix this line cellForRowAt in both ViewControllers

    // Now you need to get the UserModel.userFirstName and UserModel.userSurname
    cell.textLabel?.text = "\(currentFriend.userFirstName) \(currentFriend.userSurname)"
    

    Fix this line in didSelectRowAt in FriendsSearchViewController

    // This should not be a string
    var currentFriend = friends[indexPath.row]
    

    6.

    Change friends array from string to UserModel in MyFriendsViewController

    var friends = [UserModel]() {
        didSet {
            //
        }
    }
    

    7. Now to sort friends list by surname, adjust your exit segue function to sort the friends array

    @IBAction func addFriend(segue: UIStoryboardSegue) {
        guard segue.identifier == "addFriend",
            let allFriendsViewController = segue.source as? FriendsSearchViewController
        else { return }
        
        // Sort data when you come back
        friends = allFriendsViewController.userFriends.sorted {
            $0.userSurname < $1.userSurname
        }
        
        tableView.reloadData()
    }
    

    That should give you desired result

    However, please read on these topics for you to fully grasp these concepts:

    Structs & Classes

    Equatable

    Sorting arrays

    Final recommendation is to break your problem into smaller parts for yourself and also for stack overflow.

    For example here there were few different topics like how to create model, how to use struct with arrays, how to sort array.

    Having one topic per question will help you get answer sooner but it is good you are trying things and asking questions.