According to the latest WWDC for the PhotoPicker, and you can read the transcript here, iOS 17 is coming out with the ability to customize and embed the inline photo picker. Unfortunately for me and my problem, that's not publicly available until September 2023. So for now, you can do something like this to embed the picker directly into a view controller:
// Create a PHPickerConfiguration
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.selectionLimit = 4 // Set the selection limit to 4 images
configuration.filter = .images // Set the filter to images only
// Create a PHPickerViewController with the configuration
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self // Set the delegate to self
// Add the picker as a child view controller
addChild(picker)
// Set the frame of the picker, or use Auto Layout constraints
picker.view.frame = view.bounds
// Add the picker’s view as a subview
view.addSubview(picker.view)
// Notify the picker that it has been added
picker.didMove(toParent: self)
However, this still looks pretty bad in the iMessage extension because you can't turn off the extra UI so it's very cramped in the half sheet.
The other option is asking for camera roll permissions and making your own picker. The downside here is that if the user doesn't allow permissions, or selects certain photos, you're out of luck and it's really hard to get them to find where to turn the permissions back on again. Additionally, you're now on the hook for all the image management. Here's a snippet I wrote to fetch the first 20 images from the camera roll:
func loadImages() {
PHPhotoLibrary.requestAuthorization({ (status) -> Void in
switch status {
case .authorized:
break
default:
// We didn't get access
return
}
})
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 20
let result = PHAsset.fetchAssets(with: .image, options: fetchOptions)
result.enumerateObjects { (asset, _, _) in
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = true
manager.requestImage(for: asset,
targetSize: CGSize(width: 300, height: 300),
contentMode: .aspectFit,
options: option,
resultHandler: { (image, _) in
if let image = image {
self.images.append(image)
}
})
}
}
Finally, the solution I came to was to cut scope and just have a button to expand the iMessage extension to the full sheet, and then the users can select images using the system picker fullscreen. It's a bummer, but I can revisit this answer once iOS 17 drops.