Push from command line

This commit is contained in:
Deniz Duezgoeren
2019-08-12 11:20:21 +02:00
parent 3a919dcb23
commit f1345eac14
512 changed files with 103288 additions and 1930 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
// Created by bryankeller on 7/24/17.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
/// Encapsulates the vertical fitting priority for an element laid out by `MagazineLayout`.
///
/// Used by `UICollectionViewCell` and `UICollectionReusableView` subclasses to determine which
/// vertical fitting priority to pass into
/// `systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority)` via
/// `preferredLayoutAttributesFitting(_:)`.
public final class MagazineLayoutCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
/// `MagazineLayout` supports self-sizing and static-sizing in the vertical direction. The value
/// of this property will change the layout priority used for sizing.
public var shouldVerticallySelfSize = true
override public func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! MagazineLayoutCollectionViewLayoutAttributes
copy.shouldVerticallySelfSize = shouldVerticallySelfSize
return copy
}
override public func isEqual(_ object: Any?) -> Bool {
return super.isEqual(object) &&
shouldVerticallySelfSize == (object as? MagazineLayoutCollectionViewLayoutAttributes)?.shouldVerticallySelfSize
}
}
@@ -0,0 +1,27 @@
// Created by bryankeller on 9/24/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
/// `MagazineLayout`'s invalidation context type.
///
/// Used to indicate that collection view properties and/or delegate layout metrics changed.
public final class MagazineLayoutInvalidationContext: UICollectionViewLayoutInvalidationContext {
/// Indicates whether to recompute the positions and sizes of elements based on the current
/// collection view and delegate layout metrics.
public var invalidateLayoutMetrics = true
}
@@ -0,0 +1,40 @@
// Created by bryankeller on 10/18/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
extension MagazineLayout {
/// Constants for layout sizing and spacing defaults.
public enum Default {
public static let ItemSizeMode = MagazineLayoutItemSizeMode(
widthMode: .fullWidth(respectsHorizontalInsets: true),
heightMode: MagazineLayoutItemHeightMode.static(height: ItemHeight))
public static let HeaderVisibilityMode = MagazineLayoutHeaderVisibilityMode.hidden
public static let FooterVisibilityMode = MagazineLayoutFooterVisibilityMode.hidden
public static let BackgroundVisibilityMode = MagazineLayoutBackgroundVisibilityMode.hidden
public static let ItemHeight: CGFloat = 150
public static let HeaderHeight: CGFloat = 44
public static let FooterHeight: CGFloat = 44
public static let VerticalSpacing: CGFloat = 0
public static let HorizontalSpacing: CGFloat = 0
public static let SectionInsets: UIEdgeInsets = .zero
public static let ItemInsets: UIEdgeInsets = .zero
}
}
@@ -0,0 +1,27 @@
// Created by bryankeller on 10/18/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
extension MagazineLayout {
/// Constants for supported supplementary view element kinds.
public enum SupplementaryViewKind {
public static let sectionHeader = "MagazineLayoutSupplementaryViewKindSectionHeader"
public static let sectionFooter = "MagazineLayoutSupplementaryViewKindSectionFooter"
public static let sectionBackground = "MagazineLayoutSupplementaryViewKindSectionBackground"
}
}
@@ -0,0 +1,27 @@
// Created by bryankeller on 10/15/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Represents the visibility mode for a background.
public enum MagazineLayoutBackgroundVisibilityMode {
/// This visiblity mode will cause the background to be displayed behind the items and headers in
/// its respective section.
case visible
/// This visibility mode will cause the background to not be visibile behind the items and headers
/// in its respective section.
case hidden
}
@@ -0,0 +1,78 @@
// Created by Roman Laitarenko on 2/4/19.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CoreGraphics
// MARK: - MagazineLayoutFooterVisibilityMode
/// Represents the visibility mode for a footer.
public enum MagazineLayoutFooterVisibilityMode {
/// This visibility mode will cause the footer to be displayed using the specified height mode in
/// its respective section. If `pinToVisibleBounds` is true, the footer will pin to the visible
/// bounds of the collection view while its containing section is visible.
case visible(heightMode: MagazineLayoutFooterHeightMode, pinToVisibleBounds: Bool)
/// This visibility mode will cause the footer to not be visibile in its respective section.
case hidden
/// This visibility mode will cause the footer to be displayed using the specified height mode in
/// its respective section.
public static func visible(
heightMode: MagazineLayoutFooterHeightMode)
-> MagazineLayoutFooterVisibilityMode
{
return .visible(heightMode: heightMode, pinToVisibleBounds: false)
}
}
// MARK: - MagazineLayoutFooterHeightMode
/// Represents the vertical sizing mode for a footer.
public enum MagazineLayoutFooterHeightMode {
/// This height mode will force the footer to be displayed with a height equal to `height`.
///
/// To properly support multiline labels, dynamic type, and other technologies that could affect
/// the height of your footers dynamically, consider using the `dynamic` height mode.
case `static`(height: CGFloat)
/// This height mode will cause the footer to self-size in the vertical direction.
///
/// In practice, self-sizing in the vertical direction means that the footer will get its height
/// from the Auto Layout engine. Use this height mode for footers whose height is not known
/// upfront. For example, if you support multiline labels or dynamic type, your height is likely
/// not known until the Auto Layout engine resolves the layout at runtime.
case dynamic
}
// MARK: Equatable
extension MagazineLayoutFooterHeightMode: Equatable {
public static func == (
lhs: MagazineLayoutFooterHeightMode,
rhs: MagazineLayoutFooterHeightMode)
-> Bool
{
switch (lhs, rhs) {
case (.static(let l), .static(let r)): return l == r
case (.dynamic, .dynamic): return true
default: return false
}
}
}
@@ -0,0 +1,79 @@
// Created by bryankeller on 10/15/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CoreGraphics
// MARK: - MagazineLayoutHeaderVisibilityMode
/// Represents the visibility mode for a header.
public enum MagazineLayoutHeaderVisibilityMode {
/// This visibility mode will cause the header to be displayed using the specified height mode in
/// its respective section. If `pinToVisibleBounds` is true, the header will pin to the visible
/// bounds of the collection view while its containing section is visible.
case visible(heightMode: MagazineLayoutHeaderHeightMode, pinToVisibleBounds: Bool)
/// This visibility mode will cause the header to not be visibile in its respective section.
case hidden
/// This visibility mode will cause the header to be displayed using the specified height mode in
/// its respective section.
public static func visible(
heightMode: MagazineLayoutHeaderHeightMode)
-> MagazineLayoutHeaderVisibilityMode
{
return .visible(heightMode: heightMode, pinToVisibleBounds: false)
}
}
// MARK: - MagazineLayoutHeaderHeightMode
/// Represents the vertical sizing mode for a header.
public enum MagazineLayoutHeaderHeightMode {
/// This height mode will force the header to be displayed with a height equal to `height`.
///
/// To properly support multiline labels, dynamic type, and other technologies that could affect
/// the height of your headers dynamically, consider using the `dynamic` height mode.
case `static`(height: CGFloat)
/// This height mode will cause the header to self-size in the vertical direction.
///
/// In practice, self-sizing in the vertical direction means that the header will get its height
/// from the Auto Layout engine. Use this height mode for headers whose height is not known
/// upfront. For example, if you support multiline labels or dynamic type, your height is likely
/// not known until the Auto Layout engine resolves the layout at runtime.
case dynamic
}
// MARK: Equatable
extension MagazineLayoutHeaderHeightMode: Equatable {
public static func == (
lhs: MagazineLayoutHeaderHeightMode,
rhs: MagazineLayoutHeaderHeightMode)
-> Bool
{
switch (lhs, rhs) {
case (.static(let l), .static(let r)): return l == r
case (.dynamic, .dynamic): return true
default: return false
}
}
}
@@ -0,0 +1,164 @@
// Created by bryankeller on 10/15/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CoreGraphics
// MARK: - MagazineLayoutItemSizeMode
/// Represents the horizontal and vertical sizing mode for an item.
public struct MagazineLayoutItemSizeMode {
// MARK: Lifecycle
public init(widthMode: MagazineLayoutItemWidthMode, heightMode: MagazineLayoutItemHeightMode) {
self.widthMode = widthMode
self.heightMode = heightMode
}
// MARK: Public
/// The width mode for the item.
public let widthMode: MagazineLayoutItemWidthMode
/// The height mode for the item.
public let heightMode: MagazineLayoutItemHeightMode
}
// MARK: - MagazineLayoutItemWidthMode
/// Represents the horizontal sizing mode for an item.
///
/// Consecutive items with the same width mode will display on the same row until there is no more
/// room.
public enum MagazineLayoutItemWidthMode {
/// Full width items will fill the available width in a section.
///
/// Use this width mode to create lists of items.
/// `respectsHorizontalInsets` specifies whether the item should be edge-to-edge in a section, or
/// if it should be inset by the item insets specified for a section. `respectsHorizontalInsets`
/// does not take into account section insets or the collection view's content inset.
case fullWidth(respectsHorizontalInsets: Bool)
/// Fractional width items will take up `1/divisor` of the available width for a given row of
/// items.
///
/// Use this width mode to create grids of items. Consider using `halfWidth`, `thirdWidth`,
/// `fourthWidth`, or `fifthWidth`, which are equivalent to using `fractionalWidth` with a
/// `divisor` of `2`, `3`, `4`, or `5`, respectively.
///
/// Fractional width items respect `contentInset.left` and `contentInset.right`, and are affected
/// by the horizontal spacing specified for the section in which they're contained. On iOS 11 and
/// higher, they will also take the safe area insets into account if the collection view's
/// `contentInsetAdjustmentBehavior` property is set to a value that respects the safe area.
///
/// - Warning: `divisor` must be greater than `0`. Specifying `0` as the `divisor` is a programmer
/// error and **will result in a runtime crash**.
case fractionalWidth(divisor: UInt)
/// Half width items will take up `1/2` of the available width for a given row of items.
public static var halfWidth: MagazineLayoutItemWidthMode {
return .fractionalWidth(divisor: 2)
}
/// Third width items will take up `1/3` of the available width for a given row of items.
public static var thirdWidth: MagazineLayoutItemWidthMode {
return .fractionalWidth(divisor: 3)
}
/// Fourth width items will take up `1/4` of the available width for a given row of items.
public static var fourthWidth: MagazineLayoutItemWidthMode {
return .fractionalWidth(divisor: 4)
}
/// Fifth width items will take up `1/5` of the available width for a given row of items.
public static var fifthWidth: MagazineLayoutItemWidthMode {
return .fractionalWidth(divisor: 5)
}
}
// MARK: Equatable
extension MagazineLayoutItemWidthMode: Equatable {
public static func == (
lhs: MagazineLayoutItemWidthMode,
rhs: MagazineLayoutItemWidthMode)
-> Bool
{
switch (lhs, rhs) {
case (.fullWidth(let l), .fullWidth(let r)): return l == r
case (.fractionalWidth(let l), .fractionalWidth(let r)): return l == r
default: return false
}
}
}
// MARK: - MagazineLayoutItemHeightMode
/// Represents the vertical sizing mode for an item.
///
/// `MagazineLayout` supports vertically self-sizing and statically sized items. Since height modes
/// are specified for each item, you can mix vertically self-sizing and statically sized items in
/// the same sections, and even in the same rows.
public enum MagazineLayoutItemHeightMode {
/// This height mode mode will cause the item to be displayed with a height equal to `height`.
///
/// To properly support multiline labels, dynamic type, and other technologies that could affect
/// the height of your items dynamically, consider using one of the dynamic height modes.
case `static`(height: CGFloat)
/// This height mode will cause the item to self-size in the vertical direction.
///
/// In practice, self-sizing in the vertical direction means that the item will get its height
/// from the Auto Layout engine. Use this height mode for items whose height is not known upfront.
/// For example, if you support multiline labels or dynamic type, your height is likely not known
/// until the Auto Layout engine resolves the layout at runtime.
case dynamic
/// This height mode will cause the item to self-size in the vertical direction, then resize to
/// match the height of the tallest item in the same row of items.
///
/// If the item _is_ the tallest item in the row (after being self-sized), then it will stay
/// at its self-sized height until it's no longer the tallest item in the row.
///
/// Note that items with this height mode will resize to match the height of the tallest item in
/// the same row of items, even if the tallest item has a `static` height mode.
case dynamicAndStretchToTallestItemInRow
}
// MARK: Equatable
extension MagazineLayoutItemHeightMode: Equatable {
public static func == (
lhs: MagazineLayoutItemHeightMode,
rhs: MagazineLayoutItemHeightMode)
-> Bool
{
switch (lhs, rhs) {
case (.static(let l), .static(let r)): return l == r
case (.dynamic, .dynamic): return true
case (.dynamicAndStretchToTallestItemInRow, .dynamicAndStretchToTallestItemInRow): return true
default: return false
}
}
}
@@ -0,0 +1,138 @@
// Created by bryankeller on 7/17/17.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
public protocol UICollectionViewDelegateMagazineLayout: UICollectionViewDelegate {
/// Asks the delegate for the size mode of the specified item.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - indexPath: The index path of the item.
///
/// - Returns: The size mode of the specified item.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeModeForItemAt indexPath: IndexPath)
-> MagazineLayoutItemSizeMode
/// Asks the delegate for the visibility mode of the header in the specified section.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section containing the header.
///
/// - Returns: The visibility mode of the header in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
visibilityModeForHeaderInSectionAtIndex index: Int)
-> MagazineLayoutHeaderVisibilityMode
/// Asks the delegate for the visibility mode of the footer in the specified section.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section containing the footer.
///
/// - Returns: The visibility mode of the footer in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
visibilityModeForFooterInSectionAtIndex index: Int)
-> MagazineLayoutFooterVisibilityMode
/// Asks the delegate for the visibility mode of the background in the specified section.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section containing the background.
///
/// - Returns: The visibility mode of the background in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
visibilityModeForBackgroundInSectionAtIndex index: Int)
-> MagazineLayoutBackgroundVisibilityMode
/// Asks the delegate for the horizontal spacing for items in the specified section.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section whose horizontal item spacing is needed.
///
/// - Returns: The horizontal spacing for items in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
horizontalSpacingForItemsInSectionAtIndex index: Int)
-> CGFloat
/// Asks the delegate for the vertical spacing for items in the specified section.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section whose vertical item spacing is needed.
///
/// - Returns: The vertical spacing for items in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
verticalSpacingForElementsInSectionAtIndex index: Int)
-> CGFloat
/// Asks the delegate for the amount by which to inset elements in the specified section.
///
/// Section insets are relative to the content's bounds, which is impacted by the collection
/// view's content inset.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section whose element insets are needed.
///
/// - Returns: The amount by which to inset elements in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetsForSectionAtIndex index: Int)
-> UIEdgeInsets
/// Asks the delegate for the amount by which to inset items in the specified section.
///
/// Item insets are relative to the section's bounds, which is impacted by the section's insets
/// and, transitively, the collection view's content inset.
///
/// - Parameters:
/// - collectionView: The collection view using the layout.
/// - collectionViewLayout: The layout requesting the information.
/// - index: The index of the section whose item insets are needed.
///
/// - Returns: The amount by which to inset items in the specified section.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetsForItemsInSectionAtIndex index: Int)
-> UIEdgeInsets
}
@@ -0,0 +1,61 @@
// Created by bryankeller on 11/29/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
/// A collection reusable view that coordinates with `MagazineLayoutCollectionViewLayoutAttributes`
/// to determine how to size itself: with self-sizing, or without self-sizing. Use this class
/// (or subclasses) for displaying header and background supplementary views with `MagazineLayout`.
///
/// Note that this class is very similar to `MagazineLayoutCollectionViewCell`.
///
/// `UIKit` invokes `preferredLayoutAttributesFitting(_:)` with an initial set of layout attributes,
/// giving this reusable view subclass a chance to modify the `size` property of the attributes
/// based on whether or not we want to self-size.
///
/// Subclassing and/or adding additional protocol conformances is encouraged, although modifying
/// the behavior of `preferredLayoutAttributesFitting(_:)` is not recommended.
///
/// This class exists because `MagazineLayout` supports self-sizing supplementary views in just the
/// vertical dimension - a use case that `UICollectionReusableView` does not support out-of-the-box.
open class MagazineLayoutCollectionReusableView: UICollectionReusableView {
override open func preferredLayoutAttributesFitting(
_ layoutAttributes: UICollectionViewLayoutAttributes)
-> UICollectionViewLayoutAttributes
{
guard let attributes = layoutAttributes as? MagazineLayoutCollectionViewLayoutAttributes else {
assertionFailure("`layoutAttributes` must be an instance of `MagazineLayoutCollectionViewLayoutAttributes`")
return super.preferredLayoutAttributesFitting(layoutAttributes)
}
let size: CGSize
if attributes.shouldVerticallySelfSize {
// Self-sizing is required in the vertical dimension.
size = super.systemLayoutSizeFitting(
layoutAttributes.size,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
} else {
// No self-sizing is required; respect whatever size the layout determined.
size = layoutAttributes.size
}
layoutAttributes.size = size
return layoutAttributes
}
}
@@ -0,0 +1,88 @@
// Created by bryankeller on 11/29/18.
// Copyright © 2018 Airbnb, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import UIKit
/// A cell that coordinates with `MagazineLayoutCollectionViewLayoutAttributes` to determine how to
/// size itself: with self-sizing, or without self-sizing. Use this class (or subclasses) for
/// displaying cells with `MagazineLayout`.
///
/// Note that this class is very similar to `MagazineLayoutCollectionReusableView`.
///
/// `UIKit` invokes `preferredLayoutAttributesFitting(_:)` with an initial set of layout attributes,
/// giving this cell subclass a chance to modify the `size` property of the attributes based on
/// whether or not we want to self-size.
///
/// Subclassing and/or adding additional protocol conformances is encouraged, although modifying
/// the behavior of `preferredLayoutAttributesFitting(_:)` is not recommended.
///
/// This class exists because `MagazineLayout` supports self-sizing in just the vertical dimension -
/// a use case that `UICollectionViewCell` does not support out-of-the-box.
///
/// As of iOS 12, `UICollectionReusableView` is tightly coupled with `UICollectionViewFlowLayout`'s
/// private `_estimatesSizes` property, which is used to determine how to size cells displayed in a
/// `UICollectionViewFlowLayout`. If `_estimatesSizes` is `true`, then `UICollectionReusableView`
/// will self-size in both the horizontal and vertical dimensions. If it is `false`, no self-sizing
/// will occur. In short, `UICollectionReusableView` is only optimized to work correctly with
/// Apple's own layout.
open class MagazineLayoutCollectionViewCell: UICollectionViewCell {
override open func preferredLayoutAttributesFitting(
_ layoutAttributes: UICollectionViewLayoutAttributes)
-> UICollectionViewLayoutAttributes
{
guard let attributes = layoutAttributes as? MagazineLayoutCollectionViewLayoutAttributes else {
assertionFailure("`layoutAttributes` must be an instance of `MagazineLayoutCollectionViewLayoutAttributes`")
return super.preferredLayoutAttributesFitting(layoutAttributes)
}
// In some cases, `contentView`'s required width and height constraints
// (created from its auto-resizing mask) will not have the correct constants before invoking
// `systemLayoutSizeFitting(...)`, causing the cell to size incorrectly. This seems to be a
// UIKit bug.
// https://openradar.appspot.com/radar?id=5025850143539200
// The issue seems most common when the collection view's bounds change (on rotation).
// We correct for this by updating `contentView.bounds`, which updates the constants used by the
// width and height constraints created by the `contentView`'s auto-resizing mask.
if contentView.bounds.width != layoutAttributes.size.width {
contentView.bounds.size.width = layoutAttributes.size.width
}
if
!attributes.shouldVerticallySelfSize &&
contentView.bounds.height != layoutAttributes.size.height
{
contentView.bounds.size.height = layoutAttributes.size.height
}
let size: CGSize
if attributes.shouldVerticallySelfSize {
// Self-sizing is required in the vertical dimension.
size = super.systemLayoutSizeFitting(
layoutAttributes.size,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
} else {
// No self-sizing is required; respect whatever size the layout determined.
size = layoutAttributes.size
}
layoutAttributes.size = size
return layoutAttributes
}
}