3

I have Contact model and I want to sort by [firstLetter, fullName] into [section,row] for UITableView in this way "FAVORITES" at startIndex, "#" at endIndex and between them are sorted letters. For example: ["A", "C", "E", "#", "FAVORITES"] into ["FAVORITES", "A", "C", "E", "#"]

This is model

class Contact: Object {
    dynamic var fullName = ""
    dynamic var firstLetter = ""
}

Here's UITableViewController, where I sort data

class ContactsController: UITableViewController {

    // some code

    var contacts = try! Realm().objects(Contact.self).filter("isDeleted == false").sorted(by: ["firstLetter","fullName"])
    var sections: [String] {
        return Set(contacts.value(forKeyPath: "firstLetter") as![String]).sorted()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        setUI()
    }

    // another code
}

Edit #1


Sorry, I forgot to say that "#" contains contacts where fullName begins with special characters

Answer for my question


var sections: [String] {
        return Set(contacts.value(forKeyPath: "firstLetter") as![String]).sorted {
            //Favourites sorts before all else
            if $0 == "FAVORITES" { return true }
            if $1 == "FAVORITES" { return false }

            // Symbols sort after all else
            if $0 == "#" { return false }
            if $1 == "#" { return true }

            return $0 < $1
        }
    }
8
  • Why not add a property isFavorite to Contact. Then have two arrays for favorites and non favorites. Sort each by letter, then concatenate them. Commented Jun 21, 2017 at 17:05
  • Thanks for suggestion. What about "#", where I store contacts that starts with special character? I think it's a not good practice. Commented Jun 21, 2017 at 17:11
  • @SahandTheGreat, if you add an isFavorite property to Contact, then there's no need for two separate arrays, since you can just filter them like contacts.filter{ $0.isFavorite } or contacts.filter{ !$0.isFavorite }. Commented Jun 21, 2017 at 17:40
  • Why does your Contact store the first letter if it's already in the fullName? Commented Jun 21, 2017 at 17:42
  • @Alexander It's weird, but Realm doesn't support case ignorance for non-latin strings, so I convert to first letter of fullName to upperCase() to be similar for any case Commented Jun 21, 2017 at 18:11

2 Answers 2

1

Here is how I would do this. I had to change the data structure for testing purposes, but you can change it back to a class that inherits from Object.

public extension Sequence {
    public typealias Element = Iterator.Element
    public typealias Group = [Element]

    public func group<Key: Hashable>(by deriveKey: (Element) -> Key) -> [Key: Group] {
        var groups =  [Key: Group]()

        for element in self {
            let key = deriveKey(element)

            if var existingArray = groups[key] { // Group already exists for this key
                groups[key] = nil //performance optimisation to prevent CoW
                existingArray.append(element)
                groups[key] = existingArray
            }
            else {
                groups[key] = [element] // Create new group
            }
        }

        return groups
    }
}


struct Contact: CustomStringConvertible {
    let fullName: String
    let isFavourte: Bool

    var firstLetter: Character {
        return fullName.characters.first!
    }

    var containsSpecialLetters: Bool {
        return false // implement me
    }

    init(_ fullName: String, _ isFavourte: Bool) {
        self.fullName = fullName
        self.isFavourte = isFavourte
    }

    var description: String { return fullName }
}

let contacts = [
    Contact("Alice", false),
    Contact("Alex", false),
    Contact("Bob", true),
    Contact("Bill", false),
    Contact("Cory", false),
    Contact("Casey", true),
    Contact("#åœ∑´ß", false),
    Contact("D", false),
    Contact("E", false),
    Contact("F", false),
    Contact("G", false),
    Contact("H", false),
]


let sections = contacts.group{ contact -> String in
    switch contact {
        case _ where contact.isFavourte: return "Favourites"
        case _ where contact.containsSpecialLetters: return "#"
        case _: return String(contact.firstLetter)
    }
}

let sectionHeaders = sections.keys.sorted {
    //Favourites sorts before all else
    if $0 == "Favourites" { return true }
    if $1 == "Favourites" { return false }

    // Symbols sort after all else
    if $0 == "#" { return false }
    if $1 == "#" { return true }

    return $0 < $1
}

for (sectionHeader, sectionContacts) in sectionHeaders.map({($0, sections[$0]!)}) {
    print("===== \(sectionHeader) =====")
    sectionContacts.forEach{ print($0) }
}

Output:

===== Favourites =====

Bob

Casey

===== A =====

Alice

Alex

===== B =====

Bill

===== C =====

Cory

===== D =====

D

===== E =====

E

===== F =====

F

===== G =====

G

===== H =====

H

===== # =====

åœ∑´ß

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for answer! I just used one function from your code sections.keys.sorted{}.
0

If I understand what you are trying to do, you may first retrieve your favorites and sort them.

Then you retrieve those starting with #.

After that you retrieve the rest and sort them.

At that point you just append the third set, and then the second, to the "favorites" array.

So the result will be an [Contact] with FAVORITES first then contacts starting with A, with B, etc., and finally those starting with #

I assume you already have the contacts, since I've never used Realm.io and that your contacts or your table do have an isFavorite flag.

Pseudo code:

func sortContacts(_ aList: [Contact]) -> [Contact]
{
    var retValue = [Contact]()

    var favorites = Set(aList.value(forKeyPath: "isFavorite == true") as![String]).sorted()

    var theRest = Set(aList.value(forKeyPath: "firstLetter") as![String]).sorted()

    retValue.append(favorites)
    retValue.append(theRest)

    return retValue
}

and you should call it in viewWillAppear for example as:

func viewWillAppear(animated: Bool)
{
    super.viewWillAppear(animated)
    sections = sortContacts(contacts)

    // code
}

1 Comment

I modified my answer with a pseudocode @DamirShakenov

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.