Skip to Content
Course content

58: Practice Exercise: Building a Command-Line Contact Manager

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How should I structure the data so it's easy to manage?

When I'm building something like this, I always start by separating the "thing" (the data) from the "logic" (how we handle that data). You don't want your main loop cluttered with the details of how a contact is stored. I'd recommend a simple struct for the Contact and a class to act as your manager.

struct Contact: Codable {
    let name: String
    let phone: String
    let email: String
}

class ContactManager {
    var contacts: [Contact] = []
    
    func add(_ contact: Contact) {
        contacts.append(contact)
    }
    
    func remove(at index: Int) {
        guard index >= 0 && index < contacts.count else { return }
        contacts.remove(at: index)
    }
}

Using a struct for the contact makes it a value type, which is exactly what you want for a data record. The ContactManager class then keeps the state of your list alive throughout the session.

What's the cleanest way to handle the user input loop?

This is where a lot of people get tangled up. You need a loop that stays open until the user explicitly tells it to quit. I usually go with a while true loop combined with a switch statement. It's readable and makes it trivial to add new commands later.

One thing to watch out for: readLine() returns an optional string, so you'll want to handle that. I like using a guard or a simple optional binding to keep things tidy.

var manager = ContactManager()
var shouldExit = false

while !shouldExit {
    print("\n1. Add Contact\n2. List Contacts\n3. Exit")
    print("Choose an option: ", terminator: "")
    
    guard let choice = readLine() else { continue }
    
    switch choice {
    case "1":
        print("Enter name: ", terminator: "")
        let name = readLine() ?? "Unknown"
        print("Enter phone: ", terminator: "")
        let phone = readLine() ?? "Unknown"
        manager.add(Contact(name: name, phone: phone, email: ""))
    case "2":
        for (index, contact) in manager.contacts.enumerated() {
            print("\(index): \(contact.name) - \(contact.phone)")
        }
    case "3":
        shouldExit = true
    default:
        print("Invalid option, try again.")
    }
}

How do I stop the contacts from disappearing every time I restart the app?

Since we already made the Contact struct conform to Codable, we're halfway there. To make this a "real" tool, you need to write that data to the disk. In a command-line tool, the easiest path is saving a JSON file to the local directory.

I've found that creating a helper method within the manager to handle saving and loading keeps the main loop clean. You'll use JSONEncoder to turn your array into data and Data.write(to:) to put it on the hard drive.

extension ContactManager {
    func saveToDisk() {
        let url = URL(fileURLWithPath: "contacts.json")
        do {
            let data = try JSONEncoder().encode(contacts)
            try data.write(to: url)
        } catch {
            print("Save failed: \(error)")
        }
    }
    
    func loadFromDisk() {
        let url = URL(fileURLWithPath: "contacts.json")
        guard let data = try? Data(contentsOf: url) else { return }
        if let decoded = try? JSONDecoder().decode([Contact].self, from: data) {
            self.contacts = decoded
        }
    }
}

Just remember to call loadFromDisk() when the app starts and saveToDisk() whenever a contact is added or removed. It's a simple pattern, but it's the foundation of almost every data-driven app you'll ever write.




📋 Practical Task

Exercise: Build the Persistent Contact CLI Tool

Now it's your turn to put these pieces together. Your goal is to build a fully functional Command-Line Contact Manager that survives a program restart.

  • Requirement 1: Implement a Contact struct and a ContactManager class.
  • Requirement 2: Build a menu system that allows the user to:
    • Add a new contact (Name, Phone, and Email).
    • List all existing contacts with an index number.
    • Delete a contact by providing their index number.
    • Exit the program.
  • Requirement 3: Integrate Codable so that the contact list is automatically loaded from contacts.json upon startup and saved whenever the list is modified.
  • Requirement 4: Add basic validation to ensure the user doesn't crash the program by entering a non-numeric value when deleting a contact.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.