I’m utilizing Compositional Format plus Diffable Information Supply to show photographs in a UICollectionView from Photograph Album utilizing PhotoKit’s Cached Picture Supervisor in a single column.
I would love the picture cell to be all the width of the gathering view and the peak of the cell to be scaled to the peak of the picture utilizing Facet Match content material mode.
Once I create the format I take advantage of estimated top for each merchandise and group format objects. However the preliminary top of every cell stays on the estimated top. As quickly as I begin scrolling, a few of the cells do really resize the peak appropriately, however not at all times.
Right here is the pattern code (exchange the default ViewController logic in a pattern iOS challenge with the next code):
import UIKit
import Photographs
import PhotosUI
class ViewController: UIViewController, UICollectionViewDelegate {
enum Part: CaseIterable {
case important
}
var fetchResult: PHFetchResult<PHAsset>!
var dataSource: UICollectionViewDiffableDataSource<Part, PHAsset>!
var collectionView: UICollectionView!
var emptyAlbumMessageView : UIView! = nil
let imageManager = PHCachingImageManager()
var selectedAssets: [PHAsset] {
var pAssets = [PHAsset]()
fetchResult.enumerateObjects { (asset, index, cease) in
pAssets.append(asset)
}
return pAssets
}
override func viewDidLoad() {
tremendous.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
createEmptyAlbumMessageView()
configurePhotoData()
configureHierarchy()
configureDataSource()
displayPhotos(fetchResult!, title: "All Photographs")
}
func createEmptyAlbumMessageView() {
emptyAlbumMessageView = UIView()
emptyAlbumMessageView.backgroundColor = .black
view.addSubview(emptyAlbumMessageView)
emptyAlbumMessageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emptyAlbumMessageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
emptyAlbumMessageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
emptyAlbumMessageView.topAnchor.constraint(equalTo: view.topAnchor),
emptyAlbumMessageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Title Label
let titleLabel : UILabel = UILabel()
titleLabel.textual content = "Empty Album"
titleLabel.textAlignment = .middle
titleLabel.font = UIFont.boldSystemFont(ofSize: 21.0)
emptyAlbumMessageView.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 100),
titleLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
titleLabel.centerXAnchor.constraint(equalTo: emptyAlbumMessageView.centerXAnchor, constant: -30),
titleLabel.centerYAnchor.constraint(equalTo: emptyAlbumMessageView.centerYAnchor)])
// Message Label
let messageLabel : UILabel = UILabel(body: CGRect(x: 290, y: 394, width: 294, top: 80))
messageLabel.textual content = "This album is empty. Add some images to it within the Photographs app and they're going to seem right here routinely."
messageLabel.font = UIFont.systemFont(ofSize: 17.0)
messageLabel.numberOfLines = 3
messageLabel.textAlignment = .middle
messageLabel.lineBreakMode = .byWordWrapping
emptyAlbumMessageView.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
messageLabel.widthAnchor.constraint(equalToConstant: 294),
messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 80),
messageLabel.centerXAnchor.constraint(equalTo: titleLabel.centerXAnchor),
messageLabel.firstBaselineAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor, constant: 10)
])
self.view.bringSubviewToFront(emptyAlbumMessageView)
self.emptyAlbumMessageView.isHidden = true
}
func configurePhotoData() {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
allPhotosOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.picture.rawValue)
fetchResult = PHAsset.fetchAssets(with: allPhotosOptions)
}
func configureHierarchy() {
let collectionView = UICollectionView(body: view.bounds, collectionViewLayout: createLayout())
collectionView.delegate = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
if #out there(iOS 11.0, *) {
let safeArea = self.view.safeAreaLayoutGuide
collectionView.topAnchor.constraint(equalTo: safeArea.topAnchor, fixed: 0).isActive = true
} else {
let topGuide = self.topLayoutGuide
collectionView.topAnchor.constraint(equalTo: topGuide.bottomAnchor, fixed: 0).isActive = true
}
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.collectionView = collectionView
self.collectionView.scrollsToTop = false
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration
<PhotoThumbnailCollectionViewCell, PHAsset> { [weak self] cell, indexPath, asset in
guard let self = self else { return }
let scale = UIScreen.important.scale
cell.contentMode = .scaleAspectFit
let imageViewFrameWidth = self.collectionView.body.width
let imageViewFrameHeight = (Double(asset.pixelHeight)/scale) / (Double(asset.pixelWidth)/scale) * imageViewFrameWidth
let thumbnailSize = CGSize(width: imageViewFrameWidth * scale, top: imageViewFrameHeight * scale)
cell.representedAssetIdentifier = asset.localIdentifier
self.imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: cell.contentMode == .scaleAspectFit ? .aspectFit : .aspectFill, choices: nil, resultHandler: { picture, _ in
if cell.representedAssetIdentifier == asset.localIdentifier {
cell.picture = picture
}
})
cell.layoutIfNeeded()
}
dataSource = UICollectionViewDiffableDataSource<Part, PHAsset>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, asset: PHAsset) -> UICollectionViewCell? in
let cell = collectionView.dequeueConfiguredReusableCell(utilizing: cellRegistration, for: indexPath, merchandise: asset)
return cell
}
}
func createLayout() -> UICollectionViewLayout {
let format = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection in
let estimateHeight : Float = 200
let itemWidthDimension = NSCollectionLayoutDimension.fractionalWidth(1.0)
let itemHeightDimension = NSCollectionLayoutDimension.estimated(CGFloat(estimateHeight))
let itemSize = NSCollectionLayoutSize(widthDimension: itemWidthDimension,
heightDimension: itemHeightDimension)
let itemLayout = NSCollectionLayoutItem(layoutSize: itemSize)
let groupWidthDimension = NSCollectionLayoutDimension.fractionalWidth(1.0)
let groupHeightDimension = NSCollectionLayoutDimension.estimated(CGFloat(estimateHeight))
let groupSize = NSCollectionLayoutSize(widthDimension: groupWidthDimension,
heightDimension: groupHeightDimension )
let groupLayout = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [itemLayout])
let sectionLayout = NSCollectionLayoutSection(group: groupLayout)
return sectionLayout
}
return format
}
public func displayPhotos(_ fetchResult: PHFetchResult<PHAsset>, title: String?) {
self.fetchResult = fetchResult
self.title = title
updateSnapshot(animate: false)
scrollToBottom()
}
func updateSnapshot(animate: Bool = false, reload: Bool = true) {
self.emptyAlbumMessageView.isHidden = !(0 == fetchResult.depend)
var snapshot = NSDiffableDataSourceSnapshot<Part, PHAsset>()
snapshot.appendSections([.main])
let selectedAssets = selectedAssets
snapshot.appendItems(selectedAssets)
if true == reload {
snapshot.reloadItems(selectedAssets)
} else {
snapshot.reconfigureItems(selectedAssets)
}
dataSource.apply(snapshot, animatingDifferences: animate)
}
public func scrollToBottom() {
collectionView.layoutIfNeeded()
DispatchQueue.important.async { [self] in
self.collectionView!.scrollToItem(at: IndexPath(row: fetchResult.count-1, part: 0), at: .backside, animated: false)
}
}
}
class PhotoThumbnailCollectionViewCell: UICollectionViewCell {
var picture: UIImage? {
didSet {
setNeedsUpdateConfiguration()
}
}
override init(body: CGRect) {
tremendous.init(body: body)
}
override func updateConfiguration(utilizing state: UICellConfigurationState) {
var config = PhotoThumbnailCellConfiguration().up to date(for: state)
config.picture = picture
config.contentMode = self.contentMode
contentConfiguration = config
}
var representedAssetIdentifier: String!
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been applied")
}
}
struct PhotoThumbnailCellConfiguration : UIContentConfiguration {
var textual content : String? = nil
var picture: UIImage? = nil
var contentMode : UIView.ContentMode = .scaleAspectFit
func makeContentView() -> UIView & UIContentView {
return PhotoThumbnailContentView(self)
}
func up to date(for state: UIConfigurationState) -> PhotoThumbnailCellConfiguration {
return self
}
}
class PhotoThumbnailContentView : UIView, UIContentView {
var configuration: UIContentConfiguration {
didSet {
self.configure(configuration: configuration)
}
}
let imageView = UIImageView()
init(_ configuration: UIContentConfiguration) {
self.configuration = configuration
tremendous.init(body:.zero)
self.addSubview(self.imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0),
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0),
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0),
])
self.configure(configuration: configuration)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been applied")
}
func configure(configuration: UIContentConfiguration) {
guard let configuration = configuration as? PhotoThumbnailCellConfiguration else { return }
imageView.picture = configuration.picture
imageView.contentMode = configuration.contentMode
}
}
Since I’m setting the estimated top in each the merchandise and group format objects, I’d count on the cell top to get calculated routinely. For some cause, the peak solely will get calculated after scrolling, however not for ALL cells.