Follow along at https://www.hackingwithswift.com/100/43.
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
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!
}
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.
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 ⚡️.