Sunday, October 15, 2023
HomeiOS Developmentios - Swift - CompositionalLayout - assortment view cell top not calculated...

ios – Swift – CompositionalLayout – assortment view cell top not calculated primarily based on Picture top


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.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments