102 lines
4.0 KiB
Swift
102 lines
4.0 KiB
Swift
//
|
|
// SelfSizingCollectionView.swift
|
|
// VDS
|
|
//
|
|
// Created by Matt Bruce on 11/18/22.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import Combine
|
|
|
|
/// UICollectionView subclassed to deal with Changing the size of itself based on its children and layout and changes of its contentSize.
|
|
@objc(VDSSelfSizingCollectionView)
|
|
public final class SelfSizingCollectionView: UICollectionView {
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Initialization
|
|
//--------------------------------------------------
|
|
|
|
/// Initializer
|
|
/// - Parameters:
|
|
/// - frame: Frame needed
|
|
/// - layout: Layout used for this CollectionView
|
|
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
|
|
super.init(frame: frame, collectionViewLayout: layout)
|
|
self.setupContentSizeObservation()
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
self.setupContentSizeObservation()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Properties
|
|
//--------------------------------------------------
|
|
private var collectionViewHeight: NSLayoutConstraint?
|
|
private var anyCancellable: AnyCancellable?
|
|
private var contentSizeSubject = CurrentValueSubject<CGSize, Never>(.zero)
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Public Properties
|
|
//--------------------------------------------------
|
|
public var contentSizePublisher: AnyPublisher<CGSize, Never> {
|
|
contentSizeSubject.eraseToAnyPublisher()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Overrides
|
|
//--------------------------------------------------
|
|
|
|
/// The natural size for the receiving view, considering only properties of the view itself.
|
|
public override var intrinsicContentSize: CGSize {
|
|
let contentSize = self.contentSize
|
|
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
|
|
}
|
|
|
|
/// Overridden to deal with Appearance Changes
|
|
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
//print(type(of: self), #function)
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
// We need to handle any change that will affect layout and/or anything that affects size of a UILabel
|
|
if self.traitCollection.hasDifferentTextAppearance(comparedTo: previousTraitCollection) {
|
|
self.collectionViewLayout.invalidateLayout()
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Methods
|
|
//--------------------------------------------------
|
|
private func setupContentSizeObservation() {
|
|
//ensure autoLayout uses intrinsic height
|
|
setContentHuggingPriority(.required, for: .vertical)
|
|
setContentCompressionResistancePriority(.required, for: .vertical)
|
|
collectionViewHeight = heightAnchor.constraint(equalToConstant: 0).activate()
|
|
|
|
anyCancellable = self.publisher(for: \.contentSize, options: [.new])
|
|
.sink { [weak self] compare in
|
|
guard let self else { return }
|
|
if compare.height != self.collectionViewHeight?.constant {
|
|
self.invalidateIntrinsicContentSize()
|
|
self.collectionViewHeight?.constant = compare.height
|
|
self.contentSizeSubject.send(compare)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UITraitCollection {
|
|
|
|
/// Used within SelfSizingCollectionView to determine if there is an appearance change.
|
|
/// - Parameter traitCollection: TraitCollection to compare.
|
|
/// - Returns: True/False based on the trailCollection passed in.
|
|
public func hasDifferentTextAppearance(comparedTo traitCollection: UITraitCollection?) -> Bool {
|
|
var result = self.preferredContentSizeCategory != traitCollection?.preferredContentSizeCategory
|
|
result = result || self.legibilityWeight != traitCollection?.legibilityWeight
|
|
return result
|
|
}
|
|
}
|
|
|