1 |
//
|
|
2 |
// ChatLayout
|
|
3 |
// ContainerCollectionViewCell.swift
|
|
4 |
// https://github.com/ekazaev/ChatLayout
|
|
5 |
//
|
|
6 |
// Created by Eugene Kazaev in 2020-2021.
|
|
7 |
// Distributed under the MIT license.
|
|
8 |
//
|
|
9 |
|
|
10 |
import Foundation
|
|
11 |
import UIKit
|
|
12 |
|
|
13 |
/// A delegate of `ContainerCollectionViewCell`/`ContainerCollectionReusableView` should implement this methods if
|
|
14 |
/// it is required to participate in containers lifecycle.
|
|
15 |
public protocol ContainerCollectionViewCellDelegate: AnyObject {
|
|
16 |
|
|
17 |
/// Perform any clean up necessary to prepare the view for use again.
|
|
18 |
func prepareForReuse()
|
|
19 |
|
|
20 |
/// Allows to override the call of `ContainerCollectionViewCell`/`ContainerCollectionReusableView`
|
|
21 |
/// `UICollectionReusableView.preferredLayoutAttributesFitting(...)` and make the layout calculations.
|
|
22 |
///
|
|
23 |
/// **NB**: You must override it to avoid unnecessary autolayout calculations if you are providing exact cell size
|
|
24 |
/// in `ChatLayoutDelegate.sizeForItem(...)` and return `layoutAttributes` without modifications.
|
|
25 |
/// - Parameter layoutAttributes: `ChatLayoutAttributes` provided by `ChatLayout`
|
|
26 |
/// - Returns: Modified `ChatLayoutAttributes` on nil if `UICollectionReusableView.preferredLayoutAttributesFitting(...)`
|
|
27 |
/// should be called instead.
|
|
28 |
func preferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes) -> ChatLayoutAttributes?
|
|
29 |
|
|
30 |
/// Allows to additionally modify `ChatLayoutAttributes` after the `UICollectionReusableView.preferredLayoutAttributesFitting(...)`
|
|
31 |
/// call.
|
|
32 |
/// - Parameter layoutAttributes: `ChatLayoutAttributes` provided by `ChatLayout`.
|
|
33 |
/// - Returns: Modified `ChatLayoutAttributes`
|
|
34 |
func modifyPreferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes)
|
|
35 |
|
|
36 |
/// Apply the specified layout attributes to the view.
|
|
37 |
/// Keep in mind that this method can be called multiple times.
|
|
38 |
/// - Parameter layoutAttributes: `ChatLayoutAttributes` provided by `ChatLayout`.
|
|
39 |
func apply(_ layoutAttributes: ChatLayoutAttributes)
|
|
40 |
|
|
41 |
}
|
|
42 |
|
|
43 |
/// Default extension to make the methods optional for implementation in the successor
|
|
44 |
public extension ContainerCollectionViewCellDelegate {
|
|
45 |
|
|
46 |
func prepareForReuse() {}
|
! |
47 |
|
|
48 |
func preferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes) -> ChatLayoutAttributes? {
|
! |
49 |
return nil
|
! |
50 |
}
|
! |
51 |
|
|
52 |
func modifyPreferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes) {}
|
! |
53 |
|
|
54 |
func apply(_ layoutAttributes: ChatLayoutAttributes) {}
|
! |
55 |
|
|
56 |
}
|
|
57 |
|
|
58 |
/// A container `UICollectionViewCell` that constraints its contained view to its margins.
|
|
59 |
public final class ContainerCollectionViewCell<CustomView: UIView>: UICollectionViewCell {
|
|
60 |
|
|
61 |
/// Default reuse identifier is set with the class name.
|
|
62 |
public static var reuseIdentifier: String {
|
! |
63 |
return String(describing: self)
|
! |
64 |
}
|
! |
65 |
|
|
66 |
/// Contained view.
|
|
67 |
public lazy var customView = CustomView(frame: bounds)
|
|
68 |
|
|
69 |
/// An instance of `ContainerCollectionViewCellDelegate`
|
|
70 |
public weak var delegate: ContainerCollectionViewCellDelegate?
|
|
71 |
|
|
72 |
/// Initializes and returns a newly allocated view object with the specified frame rectangle.
|
|
73 |
/// - Parameter frame: The frame rectangle for the view, measured in points. The origin of the frame is relative
|
|
74 |
/// to the superview in which you plan to add it.
|
|
75 |
override init(frame: CGRect) {
|
! |
76 |
super.init(frame: frame)
|
! |
77 |
setupSubviews()
|
! |
78 |
}
|
! |
79 |
|
|
80 |
@available(*, unavailable, message: "Use init(reuseIdentifier:) instead")
|
|
81 |
/// This constructor is unavailable.
|
|
82 |
public required init?(coder aDecoder: NSCoder) {
|
! |
83 |
fatalError("init(coder:) has not been implemented")
|
! |
84 |
}
|
! |
85 |
|
|
86 |
/// Performs any clean up necessary to prepare the view for use again.
|
|
87 |
public override func prepareForReuse() {
|
! |
88 |
super.prepareForReuse()
|
! |
89 |
delegate?.prepareForReuse()
|
! |
90 |
}
|
! |
91 |
|
|
92 |
/// Gives the cell a chance to modify the attributes provided by the layout object.
|
|
93 |
/// - Parameter layoutAttributes: The attributes provided by the layout object. These attributes represent the values that the layout intends to apply to the cell.
|
|
94 |
/// - Returns: Modified `UICollectionViewLayoutAttributes`
|
|
95 |
public override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
! |
96 |
guard let chatLayoutAttributes = layoutAttributes as? ChatLayoutAttributes else {
|
! |
97 |
return super.preferredLayoutAttributesFitting(layoutAttributes)
|
! |
98 |
}
|
! |
99 |
delegate?.apply(chatLayoutAttributes)
|
! |
100 |
let resultingLayoutAttributes: ChatLayoutAttributes
|
! |
101 |
if let preferredLayoutAttributes = delegate?.preferredLayoutAttributesFitting(chatLayoutAttributes) {
|
! |
102 |
resultingLayoutAttributes = preferredLayoutAttributes
|
! |
103 |
} else if let chatLayoutAttributes = super.preferredLayoutAttributesFitting(chatLayoutAttributes) as? ChatLayoutAttributes {
|
! |
104 |
delegate?.modifyPreferredLayoutAttributesFitting(chatLayoutAttributes)
|
! |
105 |
resultingLayoutAttributes = chatLayoutAttributes
|
! |
106 |
} else {
|
! |
107 |
resultingLayoutAttributes = chatLayoutAttributes
|
! |
108 |
}
|
! |
109 |
return resultingLayoutAttributes
|
! |
110 |
}
|
! |
111 |
|
|
112 |
/// Applies the specified layout attributes to the view.
|
|
113 |
/// - Parameter layoutAttributes: The layout attributes to apply.
|
|
114 |
public override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
|
! |
115 |
guard let chatLayoutAttributes = layoutAttributes as? ChatLayoutAttributes else {
|
! |
116 |
return
|
! |
117 |
}
|
! |
118 |
super.apply(layoutAttributes)
|
! |
119 |
delegate?.apply(chatLayoutAttributes)
|
! |
120 |
}
|
! |
121 |
|
|
122 |
private func setupSubviews() {
|
! |
123 |
contentView.addSubview(customView)
|
! |
124 |
translatesAutoresizingMaskIntoConstraints = false
|
! |
125 |
insetsLayoutMarginsFromSafeArea = false
|
! |
126 |
layoutMargins = .zero
|
! |
127 |
|
! |
128 |
contentView.insetsLayoutMarginsFromSafeArea = false
|
! |
129 |
contentView.layoutMargins = .zero
|
! |
130 |
|
! |
131 |
customView.translatesAutoresizingMaskIntoConstraints = false
|
! |
132 |
customView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor).isActive = true
|
! |
133 |
customView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).isActive = true
|
! |
134 |
customView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
|
! |
135 |
customView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
|
! |
136 |
}
|
! |
137 |
|
|
138 |
}
|
|