Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foundation set up for compositional layout #109

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 65 additions & 49 deletions SectionKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,20 @@ extension ListCollectionViewAdapter {
extension ListCollectionViewAdapter {
@inlinable
public func flowDelegate(at indexPath: IndexPath) -> SectionFlowDelegate? {
controller(at: indexPath)?.flowDelegate
if #available(iOS 13.0, *) {
return controller(at: indexPath)?.layoutProvider?.flowLayoutProvider
} else {
return controller(at: indexPath)?.flowDelegate
}
}

@inlinable
public func flowDelegate(at index: Int) -> SectionFlowDelegate? {
controller(at: index)?.flowDelegate
if #available(iOS 13.0, *) {
return controller(at: index)?.layoutProvider?.flowLayoutProvider
} else {
return controller(at: index)?.flowDelegate
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ open class ListCollectionViewAdapter: NSObject, CollectionViewAdapter {
collectionView.dragDelegate = self
collectionView.dropDelegate = self
}
if #available(iOS 13.0, *),
let layout = collectionView.collectionViewLayout as? SectionKitCompositionalLayout {
layout.sections = { [weak self] in
self?.collectionViewSections ?? []
}
}
}

/**
Expand Down Expand Up @@ -90,6 +96,12 @@ open class ListCollectionViewAdapter: NSObject, CollectionViewAdapter {
collectionView.dragDelegate = self
collectionView.dropDelegate = self
}
if #available(iOS 13.0, *),
let layout = collectionView.collectionViewLayout as? SectionKitCompositionalLayout {
layout.sections = { [weak self] in
self?.collectionViewSections ?? []
}
}
}

public let context: CollectionViewContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,20 @@ extension SingleSectionCollectionViewAdapter {
extension SingleSectionCollectionViewAdapter {
@inlinable
public func flowDelegate(at indexPath: IndexPath) -> SectionFlowDelegate? {
controller(at: indexPath)?.flowDelegate
if #available(iOS 13.0, *) {
return controller(at: indexPath)?.layoutProvider?.flowLayoutProvider
} else {
return controller(at: indexPath)?.flowDelegate
}
}

@inlinable
public func flowDelegate(at index: Int) -> SectionFlowDelegate? {
controller(at: index)?.flowDelegate
if #available(iOS 13.0, *) {
return controller(at: index)?.layoutProvider?.flowLayoutProvider
} else {
return controller(at: index)?.flowDelegate
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import UIKit

/**
A base implementation of all `SectionController` datasource and delegate protocols.

Every declaration is marked `open` and can be overridden.
*/
@MainActor
Expand Down Expand Up @@ -36,6 +36,9 @@ open class BaseSectionController: SectionController,
@available(iOS 11.0, *)
open var dropDelegate: SectionDropDelegate? { self }

@available(iOS 13.0, *)
open var layoutProvider: SectionLayoutProvider? { .flowLayout(self) }

open func didUpdate(model: Any) { }

// MARK: - SectionDataSource
Expand Down Expand Up @@ -266,40 +269,77 @@ open class BaseSectionController: SectionController,

// MARK: - SectionFlowDelegate

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func sizeForItem(
at indexPath: SectionIndexPath,
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.itemSize ?? CGSize(width: 50, height: 50)
layout.flowLayout?.itemSize ?? FlowLayoutConstants.defaultItemSize
}

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func inset(using layout: UICollectionViewLayout, in context: CollectionViewContext) -> UIEdgeInsets {
(layout as? UICollectionViewFlowLayout)?.sectionInset ?? .zero
layout.flowLayout?.sectionInset ?? FlowLayoutConstants.defaultInset
}

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func minimumLineSpacing(using layout: UICollectionViewLayout, in context: CollectionViewContext) -> CGFloat {
(layout as? UICollectionViewFlowLayout)?.minimumLineSpacing ?? 10
layout.flowLayout?.minimumLineSpacing ?? FlowLayoutConstants.defaultMinimumLineSpacing
}

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func minimumInteritemSpacing(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGFloat {
(layout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing ?? 10
layout.flowLayout?.minimumInteritemSpacing ?? FlowLayoutConstants.defaultMinimumInteritemSpacing
}

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func referenceSizeForHeader(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.headerReferenceSize ?? .zero
layout.flowLayout?.headerReferenceSize ?? FlowLayoutConstants.defaultHeaderSize
}

@available(
iOS,
introduced: 6.0,
deprecated: 13.0,
message: "Please use the layoutProvider with the flowLayout type"
)
open func referenceSizeForFooter(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.footerReferenceSize ?? .zero
layout.flowLayout?.footerReferenceSize ?? FlowLayoutConstants.defaultFooterSize
}

}
47 changes: 47 additions & 0 deletions SectionKit/Sources/SectionController/SectionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public protocol SectionController: AnyObject {
@available(iOS 11.0, *)
var dropDelegate: SectionDropDelegate? { get }

@available(iOS 13.0, *)
var layoutProvider: SectionLayoutProvider? { get }

/// The model of this section controller changed.
func didUpdate(model: Any)
}
Expand All @@ -44,4 +47,48 @@ extension SectionController {

@available(iOS 11.0, *)
public var dropDelegate: SectionDropDelegate? { nil }

@available(iOS 13.0, *)
public var layoutProvider: SectionLayoutProvider? { nil }
}

@available(iOS 13.0, *)
public enum SectionLayoutProvider {
case flowLayout(FlowLayoutProvider)
case compositionalLayout(CompositionalLayoutProvider)
}

public typealias FlowLayoutProvider = SectionFlowDelegate

@MainActor
@available(iOS 13.0, *)
public struct CompositionalLayoutProvider {
/// Provide the layout section for the Compositional Layout
/// - Parameters:
/// - layoutEnvironment: the environment value for the layout
/// - Returns: The layout for the section
var layoutSection: (_ layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection

public init(
layoutSection: @escaping (any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection
) {
self.layoutSection = layoutSection
}
}

@available(iOS 13.0, *)
public extension SectionLayoutProvider {
var flowLayoutProvider: FlowLayoutProvider? {
guard case .flowLayout(let flowLayoutProvider) = self else {
return nil
}
return flowLayoutProvider
}

var compositionalLayoutProvider: CompositionalLayoutProvider? {
guard case .compositionalLayout(let compositionalLayoutProvider) = self else {
return nil
}
return compositionalLayoutProvider
}
}
27 changes: 21 additions & 6 deletions SectionKit/Sources/SectionController/SectionFlowDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,47 +76,62 @@ public protocol SectionFlowDelegate: AnyObject {
func referenceSizeForFooter(using layout: UICollectionViewLayout, in context: CollectionViewContext) -> CGSize
}

enum FlowLayoutConstants {
static let defaultItemSize = CGSize(width: 50, height: 50)
static let defaultInset: UIEdgeInsets = .zero
static let defaultMinimumLineSpacing: CGFloat = 10
static let defaultMinimumInteritemSpacing: CGFloat = 10
static let defaultHeaderSize: CGSize = .zero
static let defaultFooterSize: CGSize = .zero
}

extension SectionFlowDelegate {
public func sizeForItem(
at indexPath: SectionIndexPath,
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.itemSize ?? CGSize(width: 50, height: 50)
layout.flowLayout?.itemSize ?? FlowLayoutConstants.defaultItemSize
}

public func inset(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> UIEdgeInsets {
(layout as? UICollectionViewFlowLayout)?.sectionInset ?? .zero
layout.flowLayout?.sectionInset ?? FlowLayoutConstants.defaultInset
}

public func minimumLineSpacing(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGFloat {
(layout as? UICollectionViewFlowLayout)?.minimumLineSpacing ?? 10
layout.flowLayout?.minimumLineSpacing ?? FlowLayoutConstants.defaultMinimumLineSpacing
}

public func minimumInteritemSpacing(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGFloat {
(layout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing ?? 10
layout.flowLayout?.minimumInteritemSpacing ?? FlowLayoutConstants.defaultMinimumInteritemSpacing
}

public func referenceSizeForHeader(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.headerReferenceSize ?? .zero
layout.flowLayout?.headerReferenceSize ?? FlowLayoutConstants.defaultHeaderSize
}

public func referenceSizeForFooter(
using layout: UICollectionViewLayout,
in context: CollectionViewContext
) -> CGSize {
(layout as? UICollectionViewFlowLayout)?.footerReferenceSize ?? .zero
layout.flowLayout?.footerReferenceSize ?? FlowLayoutConstants.defaultFooterSize
}
}

extension UICollectionViewLayout {
var flowLayout: UICollectionViewFlowLayout? {
self as? UICollectionViewFlowLayout
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import UIKit

@available(iOS 13.0, *)
extension NSCollectionLayoutSection {
static let empty: NSCollectionLayoutSection = {
let layoutSize = NSCollectionLayoutSize(
widthDimension: .absolute(0),
heightDimension: .absolute(0)
)
return NSCollectionLayoutSection(
group: .vertical(
layoutSize: layoutSize,
subitem: .init(layoutSize: layoutSize),
count: 1
)
)
}()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import UIKit

/// This compositional layout is designed for the section kit.
/// It ensures that the layout provider utilizes the layout section provided by the compositional layout section controller.
@MainActor
@available(iOS 13.0, *)
final public class SectionKitCompositionalLayout: UICollectionViewCompositionalLayout {
// Use internally to bridge the sections from the adapter to the section provider.
var sections: (() -> [Section])?

public init() {
var sections: (() -> [Section])?
super.init { index, environment in
guard let sections = sections?() else {
assertionFailure("The section update closure doesn't set up correctly, please use the `CollectionViewAdapter`")
return .empty
}
guard sections.count > index else {
assertionFailure("Section index out of bound")
return .empty
}
guard case .compositionalLayout(let provider) = sections[index].controller.layoutProvider else {
assertionFailure("Please set the layout provider with `CompositionalLayoutProvider`")
return .empty
}
return provider.layoutSection(environment)
}
sections = { [weak self] in
self?.sections?() ?? []
}
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@available(*, unavailable)
override init(
sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider,
configuration: UICollectionViewCompositionalLayoutConfiguration
) {
fatalError("init(sectionProvider:configuration:) has not been implemented")
}

@available(*, unavailable)
override init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider) {
fatalError("init(sectionProvider:) has not been implemented")
}
}
Loading
Loading