Skip to content

Files

Latest commit

 

History

History

day-043

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Day 43: Project 10: Names and Faces, Part Two

Follow along at https://www.hackingwithswift.com/100/43.

📒 Field Notes

This day covers the second part of Project 10: Names and Faces in Hacking with Swift.

I have a separate repository where I've been creating projects alongside the material in the book. And you can find Project 10 here. However, I also copied it over to Day 42's folder so I could extend from where I left off.

With that in mind, Day 43 focuses on several specific topics:

  • Importing photos with UIImagePickerController
  • Custom subclasses of NSObject
  • Connecting up the people

Importing Photos with UIImagePickerController

We can use UIImagePickerController for this, but first we need to register our controller as its delegate and handle the imagePickerController(_:didFinishPickingMediaWithInfo:) function. (It's strange that this is an optional function in the protocol. I'm genuinely curious about cases where we wouldn't need to handle this 🤷‍.)

The picker gives us a nice way to retrieve the edited image, so we really just need a strategy for actually writing that to the device's disk. In our case, we can utilize the Documents directory, which is can save private information for the app and be automatically synchronized with iCloud:

func imagePickerController(
    _ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
) {
    guard let imagePicked = info[.editedImage] as? UIImage else { return }

    let fileName = UUID().uuidString
    let imageURL = getURL(forFile: fileName)

    if let jpegData = imagePicked.jpegData(compressionQuality: 0.8) {
        try? jpegData.write(to: imageURL)
    }

    people.append(Person(name: "Unknown", imageName: fileName))
    collectionView?.reloadData()

    picker.dismiss(animated: true)
}


func getURL(forFile fileName: String) -> URL {
    return getDocumentsDirectoryURL().appendingPathComponent(fileName)
}


func getDocumentsDirectoryURL() -> URL {
    return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}

Custom Subclasses of NSObject

Why subclass NSObject? I mean... we don't have to. And it's certainly nice to prefer composition over inheritance. But inheriting from NSObject makes our type instances compatible with Objective-C -- which sometimes we still need. This Quora thread has some good explanations that go into more detail.

Connecting up the People

I mentioned before how the data source methods of UICollectionView followed many of the same patterns as UITableView.

One bit of uniqueness here, though, is how we're responding to didSelectItemAt. Rather than playing off of some of the editing hooks that a table view might give us, we find the Person that was selected, and then prompt the user to for a name:

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let person = people[indexPath.item]

    promptForName(of: person)
}

func promptForName(of person: Person) {
    let alertController = UIAlertController(title: "Who is this?", message: nil, preferredStyle: .alert)

    alertController.addTextField()
    alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel))

    alertController.addAction(
        UIAlertAction(title: "OK", style: .default) { [unowned self, alertController] _ in
            let newName = alertController.textFields![0].text!

            person.name = newName
            self.collectionView?.reloadData()
        }
    )

    present(alertController, animated: true)
}

🔑 Architecturally, this is a somewhat trivial example. But I think it's a good introduction to how collection views — despite having a data flow that's similar to table views — can quickly introduce different functionalities and ways of interacting with our content ⚡️.