Unverified Commit c2a95f5e authored by Apptek Studios's avatar Apptek Studios Committed by GitHub
Browse files

Merge pull request #109 from apptekstudios/dev

Merge dev branch
parents f8ffe595 30f735dc
Showing with 576 additions and 346 deletions
+576 -346
# file options
--symlinks ignore
--swiftversion 5.1
--swiftversion 5.2
--exclude Package.swift
......
......@@ -17,7 +17,6 @@ struct AdjustableGridScreen: View
{
@ObservedObject var layoutState = LayoutState()
@State var showConfig: Bool = true
@State var animateChange: Bool = false
@State var data: [Post] = DataSource.postsForGridSection(1, number: 1000)
typealias SectionID = Int
......@@ -63,12 +62,10 @@ struct AdjustableGridScreen: View
{
VStack
{
Stepper("Number of columns", value: self.$layoutState.numberOfColumns, in: 0 ... 10)
Stepper("Number of columns", value: self.$layoutState.numberOfColumns, in: 1 ... 10)
.padding()
Stepper("Item inset", value: self.$layoutState.itemInset, in: 0 ... 5)
.padding()
Toggle(isOn: self.$animateChange) { Text("Animate layout change") }
.padding()
}
}
......@@ -83,7 +80,6 @@ struct AdjustableGridScreen: View
ASCollectionView(
section: section)
.layout(self.layout)
.shouldInvalidateLayoutOnStateChange(true, animated: self.animateChange) /// ////////////////////// TELLS ASCOLLECTIONVIEW TO INVALIDATE THE LAYOUT WHEN THE VIEW IS UPDATED
.navigationBarTitle("Adjustable Layout", displayMode: .inline)
}
.navigationBarItems(
......
......@@ -37,10 +37,12 @@ struct AppStoreScreen: View
self.onCellEvent($0, sectionID: sectionID)
})
{ item, _ in
if sectionID == 0 {
if sectionID == 0
{
AppViewFeature(app: item)
}
else if sectionID == 1 {
else if sectionID == 1
{
AppViewLarge(app: item)
}
else
......
......@@ -78,13 +78,13 @@ struct InstaFeedScreen: View
{
ASTableView(sections: sections)
.tableViewSeparatorsEnabled(false)
.tableViewOnPullToRefresh { endRefreshing in
.onPullToRefresh { endRefreshing in
print("PULL TO REFRESH")
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
endRefreshing()
}
}
.tableViewOnReachedBottom
.tableViewOnReachedBottom
{
self.loadMoreContent() // REACHED BOTTOM, LOADING MORE CONTENT
}
......
......@@ -6,14 +6,9 @@ import UIKit
class ASCollectionViewMagazineLayoutDelegate: ASCollectionViewDelegate, UICollectionViewDelegateMagazineLayout
{
override func collectionView(cellShouldSelfSizeVerticallyForItemAt indexPath: IndexPath) -> Bool
override func collectionViewSelfSizingSettings(forContext: ASSelfSizingContext) -> ASSelfSizingConfig?
{
true
}
override func collectionView(cellShouldSelfSizeHorizontallyForItemAt indexPath: IndexPath) -> Bool
{
false
ASSelfSizingConfig(selfSizeHorizontally: false, selfSizeVertically: true)
}
override var collectionViewContentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior
......
......@@ -67,10 +67,11 @@ struct MagazineLayoutScreen: View
}
}
}
func contextMenuProvider(_ post: Post) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (suggestedActions) -> UIMenu? in
let testAction = UIAction(title: "Test") { (action) in
func contextMenuProvider(_ post: Post) -> UIContextMenuConfiguration?
{
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) -> UIMenu? in
let testAction = UIAction(title: "Test") { _ in
//
}
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [testAction])
......
......@@ -25,8 +25,8 @@ struct TagsScreen: View
Spacer()
}
.onTapGesture
{
self.store.refreshStore()
{
self.store.refreshStore()
}
Text("Tags:")
.font(.title)
......@@ -49,7 +49,7 @@ struct TagsScreen: View
}
.shrinkToContentSize(isEnabled: shrinkToSize, $contentSize, dimensionToShrink: .vertical)
.collectionViewAllowCellWidthToExceedCollectionContentSize(false)
if shrinkToSize
{
Text("This is another view in the VStack, it shows how the collectionView above fits itself to the content.")
......
......@@ -7,7 +7,7 @@ import UIKit
/// THIS IS A WORK IN PROGRESS
struct WaterfallScreen: View
{
@State var data: [[Post]] = (0...10).map { DataSource.postsForWaterfallSection($0, number: 100) }
@State var data: [[Post]] = (0 ... 10).map { DataSource.postsForWaterfallSection($0, number: 100) }
@State var selectedItems: [SectionID: IndexSet] = [:]
@State var columnMinSize: CGFloat = 150
......@@ -21,49 +21,49 @@ struct WaterfallScreen: View
var sections: [ASCollectionViewSection<SectionID>]
{
data.enumerated().map { (offset, sectionData) in
data.enumerated().map { offset, sectionData in
ASCollectionViewSection(
id: offset,
data: sectionData,
onCellEvent: onCellEvent)
{ item, state in
GeometryReader
{ geom in
ZStack(alignment: .bottomTrailing)
{ geom in
ZStack(alignment: .bottomTrailing)
{
ASRemoteImageView(item.url)
.scaledToFill()
.frame(width: geom.size.width, height: geom.size.height)
.opacity(state.isSelected ? 0.7 : 1.0)
if state.isSelected
{
ASRemoteImageView(item.url)
.scaledToFill()
.frame(width: geom.size.width, height: geom.size.height)
.opacity(state.isSelected ? 0.7 : 1.0)
if state.isSelected
{
ZStack
{
Circle()
.fill(Color.blue)
Circle()
.strokeBorder(Color.white, lineWidth: 2)
Image(systemName: "checkmark")
.font(.system(size: 10, weight: .bold))
.foregroundColor(.white)
}
.frame(width: 20, height: 20)
.padding(10)
}
else
ZStack
{
Text("\(item.offset)")
.font(.title)
.bold()
.padding(2)
.background(Color(.systemBackground).opacity(0.5))
.cornerRadius(4)
.padding(10)
Circle()
.fill(Color.blue)
Circle()
.strokeBorder(Color.white, lineWidth: 2)
Image(systemName: "checkmark")
.font(.system(size: 10, weight: .bold))
.foregroundColor(.white)
}
.frame(width: 20, height: 20)
.padding(10)
}
.frame(width: geom.size.width, height: geom.size.height)
.clipped()
else
{
Text("\(item.offset)")
.font(.title)
.bold()
.padding(2)
.background(Color(.systemBackground).opacity(0.5))
.cornerRadius(4)
.padding(10)
}
}
.frame(width: geom.size.width, height: geom.size.height)
.clipped()
}
}.sectionHeader {
Text("Section \(offset)")
......@@ -167,10 +167,11 @@ struct WaterfallScreen_Previews: PreviewProvider
class WaterfallScreenLayoutDelegate: ASCollectionViewDelegate, ASWaterfallLayoutDelegate
{
func heightForHeader(sectionIndex: Int) -> CGFloat? {
func heightForHeader(sectionIndex: Int) -> CGFloat?
{
60
}
/// We explicitely provide a height here. If providing no delegate, this layout will use auto-sizing, however this causes problems if rotating the device (due to limitaitons in UICollecitonView and autosizing cells that are not visible)
func heightForCell(at indexPath: IndexPath, context: ASWaterfallLayout.CellLayoutContext) -> CGFloat
{
......
......@@ -98,15 +98,16 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
// MARK: Environment variables
//SwiftUI environment
// SwiftUI environment
@Environment(\.editMode) private var editMode
//ASCollectionView environment
// ASCollectionView environment
@Environment(\.scrollIndicatorsEnabled) private var scrollIndicatorsEnabled
@Environment(\.contentInsets) private var contentInsets
@Environment(\.alwaysBounceHorizontal) private var alwaysBounceHorizontal
@Environment(\.alwaysBounceVertical) private var alwaysBounceVertical
@Environment(\.initialScrollPosition) private var initialScrollPosition
@Environment(\.onPullToRefresh) private var onPullToRefresh
@Environment(\.collectionViewOnReachedBoundary) private var onReachedBoundary
@Environment(\.animateOnDataRefresh) private var animateOnDataRefresh
@Environment(\.attemptToMaintainScrollPositionOnOrientationChange) private var attemptToMaintainScrollPositionOnOrientationChange
......@@ -159,6 +160,7 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
updateCollectionViewSettings(collectionViewController.collectionView, delegate: context.coordinator.delegate)
context.coordinator.updateLayout()
context.coordinator.updateContent(collectionViewController.collectionView, animated: animateOnDataRefresh, refreshExistingCells: true)
context.coordinator.configureRefreshControl(for: collectionViewController.collectionView)
}
func updateCollectionViewSettings(_ collectionView: UICollectionView, delegate: ASCollectionViewDelegate?)
......@@ -191,7 +193,7 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
var parent: ASCollectionView
var delegate: ASCollectionViewDelegate?
var collectionViewController: AS_CollectionViewController?
weak var collectionViewController: AS_CollectionViewController?
var dataSource: ASCollectionViewDiffableDataSource<SectionID, ASCollectionViewItemUniqueID>?
......@@ -199,8 +201,6 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
let supplementaryReuseID = UUID().uuidString
let supplementaryEmptyKind = UUID().uuidString // Used to prevent crash if supplementaries defined in layout but not provided by the section
var hostingControllerCache = ASFIFODictionary<ASCollectionViewItemUniqueID, ASHostingControllerProtocol>()
// MARK: Private tracking variables
private var hasDoneInitialSetup = false
......@@ -234,14 +234,6 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
}
@discardableResult
func configureHostingController(forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool) -> ASHostingControllerProtocol?
{
let controller = section(forItemID: itemID)?.dataSource.configureHostingController(reusingController: hostingControllerCache[itemID], forItemID: itemID, isSelected: isSelected)
hostingControllerCache[itemID] = controller
return controller
}
func registerSupplementaries(forCollectionView cv: UICollectionView)
{
supplementaryKinds().subtracting(haveRegisteredForSupplementaryOfKind).forEach
......@@ -257,32 +249,37 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
registerSupplementaries(forCollectionView: cv)
dataSource = .init(collectionView: cv)
{ (collectionView, indexPath, itemID) -> UICollectionViewCell? in
guard
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseID, for: indexPath) as? Cell
{ [weak self] (collectionView, indexPath, itemID) -> UICollectionViewCell? in
guard let self = self else { return nil }
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseID, for: indexPath) as? Cell
else { return nil }
let isSelected = collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false
guard let hostController = self.configureHostingController(forItemID: itemID, isSelected: isSelected)
else { return cell }
cell.invalidateLayout = {
collectionView.collectionViewLayout.invalidateLayout()
cell.invalidateLayout = { [weak collectionView] in
collectionView?.collectionViewLayout.invalidateLayout()
}
cell.maxSizeForSelfSizing = ASOptionalSize(width: self.parent.allowCellWidthToExceedCollectionContentSize ? nil : collectionView.contentSize.width,
height: self.parent.allowCellHeightToExceedCollectionContentSize ? nil : collectionView.contentSize.height)
cell.selfSizeHorizontal =
self.delegate?.collectionView(cellShouldSelfSizeHorizontallyForItemAt: indexPath)
?? (collectionView.collectionViewLayout as? ASCollectionViewLayoutProtocol)?.selfSizeHorizontally
?? true
cell.selfSizeVertical =
self.delegate?.collectionView(cellShouldSelfSizeVerticallyForItemAt: indexPath)
?? (collectionView.collectionViewLayout as? ASCollectionViewLayoutProtocol)?.selfSizeVertically
?? true
cell.setupFor(
id: itemID,
hostingController: hostController)
cell.maxSizeForSelfSizing = ASOptionalSize(
width: self.parent.allowCellWidthToExceedCollectionContentSize ? nil : collectionView.contentSize.width,
height: self.parent.allowCellHeightToExceedCollectionContentSize ? nil : collectionView.contentSize.height)
// Self Sizing Settings
let selfSizingContext = ASSelfSizingContext(cellType: .content, indexPath: indexPath)
cell.selfSizingConfig =
self.parent.sections[safe: indexPath.section]?.dataSource.getSelfSizingSettings(context: selfSizingContext)
?? self.delegate?.collectionViewSelfSizingSettings(forContext: selfSizingContext)
?? (collectionView.collectionViewLayout as? ASCollectionViewLayoutProtocol)?.selfSizingConfig
?? ASSelfSizingConfig(selfSizeHorizontally: true, selfSizeVertically: true)
// Configure cell
self.section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: isSelected)
return cell
}
dataSource?.supplementaryViewProvider = { (cv, kind, indexPath) -> UICollectionReusableView? in
dataSource?.supplementaryViewProvider = { [weak self] (cv, kind, indexPath) -> UICollectionReusableView? in
guard let self = self else { return nil }
guard self.supplementaryKinds().contains(kind) else
{
let emptyView = cv.dequeueReusableSupplementaryView(ofKind: self.supplementaryEmptyKind, withReuseIdentifier: self.supplementaryReuseID, for: indexPath) as? ASCollectionViewSupplementaryView
......@@ -291,14 +288,25 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
guard let reusableView = cv.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: self.supplementaryReuseID, for: indexPath) as? ASCollectionViewSupplementaryView
else { return nil }
// Self Sizing Settings
let selfSizingContext = ASSelfSizingContext(cellType: .supplementary(kind), indexPath: indexPath)
reusableView.selfSizingConfig =
self.parent.sections[safe: indexPath.section]?.dataSource.getSelfSizingSettings(context: selfSizingContext)
?? self.delegate?.collectionViewSelfSizingSettings(forContext: selfSizingContext)
?? ASSelfSizingConfig(selfSizeHorizontally: true, selfSizeVertically: true)
if let supplementaryView = self.parent.sections[safe: indexPath.section]?.supplementary(ofKind: kind)
{
reusableView.setupFor(
id: indexPath.section,
view: supplementaryView)
} else {
}
else
{
reusableView.setupAsEmptyView()
}
return reusableView
}
setupPrefetching()
......@@ -331,7 +339,8 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
let itemID = cell.id
else { return }
self.configureHostingController(forItemID: itemID, isSelected: cell.isSelected)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
supplementaryKinds().forEach
......@@ -362,7 +371,41 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
}
func configureRefreshControl(for cv: UICollectionView)
{
guard parent.onPullToRefresh != nil else
{
if cv.refreshControl != nil
{
cv.refreshControl = nil
}
return
}
if cv.refreshControl == nil
{
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(collectionViewDidPullToRefresh), for: .valueChanged)
cv.refreshControl = refreshControl
}
}
@objc
public func collectionViewDidPullToRefresh()
{
guard let collectionView = collectionViewController?.collectionView else { return }
let endRefreshing: (() -> Void) = { [weak collectionView] in
collectionView?.refreshControl?.endRefreshing()
}
parent.onPullToRefresh?(endRefreshing)
}
func onMoveFromParent()
{
hasDoneInitialSetup = false
}
// MARK: Functions for determining scroll position (on appear, and also on orientation change)
func scrollToPosition(_ scrollPosition: ASCollectionViewScrollPosition, animated: Bool = false)
{
switch scrollPosition
......@@ -384,8 +427,9 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
func prepareForOrientationChange()
{
guard let collectionView = collectionViewController?.collectionView else { return }
if parent.attemptToMaintainScrollPositionOnOrientationChange {
if parent.attemptToMaintainScrollPositionOnOrientationChange
{
// Get centremost cell
if let indexPath = collectionView.indexPathForItem(at: CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY))
{
......@@ -407,9 +451,12 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
var transitionCentralIndexPath: IndexPath?
func getContentOffsetForOrientationChange() -> CGPoint?
{
if parent.attemptToMaintainScrollPositionOnOrientationChange {
if parent.attemptToMaintainScrollPositionOnOrientationChange
{
return transitionCentralIndexPath.flatMap(getContentOffsetToCenterCell)
} else {
}
else
{
return nil
}
}
......@@ -431,7 +478,7 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
y: max(0, min(maxOffset.y, centerCellFrame.midY - (collectionView.bounds.height / 2))))
return newOffset
}
// MARK: Functions for updating layout
func updateLayout()
......@@ -472,8 +519,9 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
// MARK: CollectionViewDelegate functions
// NOTE: These are not called directly, but rather forwarded to the Coordinator by the ASCollectionViewDelegate class
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
{
collectionViewController.map { (cell as? Cell)?.willAppear(in: $0) }
......@@ -507,7 +555,8 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
let itemID = cell.id
else { return }
updateSelectionBindings(collectionView)
configureHostingController(forItemID: itemID, isSelected: true)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)
......@@ -517,7 +566,8 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
let itemID = cell.id
else { return }
updateSelectionBindings(collectionView)
configureHostingController(forItemID: itemID, isSelected: false)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
func updateSelectionBindings(_ collectionView: UICollectionView)
......@@ -569,6 +619,7 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
// MARK: Functions for updating contentSize binding
var lastContentSize: CGSize = .zero
func didUpdateContentSize(_ size: CGSize)
{
......@@ -593,6 +644,7 @@ public struct ASCollectionView<SectionID: Hashable>: UIViewControllerRepresentab
}
// MARK: Variables used for the custom prefetching implementation
private let queuePrefetch = PassthroughSubject<Void, Never>()
private var prefetchSubscription: AnyCancellable?
private var currentlyPrefetching: Set<IndexPath> = []
......@@ -649,9 +701,11 @@ extension ASCollectionView.Coordinator
}
// MARK: Context Menu Support
@available(iOS 13.0, *)
public extension ASCollectionView.Coordinator {
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
{
guard !indexPath.isEmpty else { return nil }
return parent.sections[safe: indexPath.section]?.dataSource.getContextMenu(for: indexPath)
}
......@@ -722,6 +776,7 @@ internal protocol ASCollectionViewCoordinator: AnyObject
func didUpdateContentSize(_ size: CGSize)
func scrollViewDidScroll(_ scrollView: UIScrollView)
func onMoveToParent()
func onMoveFromParent()
}
// MARK: Custom Prefetching Implementation
......@@ -735,12 +790,13 @@ extension ASCollectionView.Coordinator
prefetchSubscription = queuePrefetch
.collect(.byTime(DispatchQueue.main, 0.1)) // .throttle CRASHES on 13.1, fixed from 13.3 but still using .collect for 13.1 compatibility
.compactMap
{ _ in
self.collectionViewController?.collectionView.indexPathsForVisibleItems
{ [weak collectionViewController] _ in
collectionViewController?.collectionView.indexPathsForVisibleItems
}
.receive(on: DispatchQueue.global(qos: .background))
.map
{ visibleIndexPaths -> [Int: [IndexPath]] in
{ [weak self] visibleIndexPaths -> [Int: [IndexPath]] in
guard let self = self else { return [:] }
let visibleIndexPathsBySection = Dictionary(grouping: visibleIndexPaths) { $0.section }.compactMapValues
{ (indexPaths) -> (section: Int, first: Int, last: Int)? in
guard let first = indexPaths.min(), let last = indexPaths.max() else { return nil }
......@@ -782,17 +838,18 @@ extension ASCollectionView.Coordinator
return toPrefetch
}
.sink
{ prefetch in
{ [weak self] prefetch in
guard let self = self else { return }
prefetch.forEach
{ sectionIndex, toPrefetch in
if !toPrefetch.isEmpty
{
self.parent.sections[safe: sectionIndex]?.dataSource.prefetch(toPrefetch)
}
let toCancel = Array(self.currentlyPrefetching.filter { $0.section == sectionIndex }.subtracting(toPrefetch))
let toCancel = self.currentlyPrefetching.filter { $0.section == sectionIndex }.subtracting(toPrefetch)
if !toCancel.isEmpty
{
self.parent.sections[safe: sectionIndex]?.dataSource.cancelPrefetch(toCancel)
self.parent.sections[safe: sectionIndex]?.dataSource.cancelPrefetch(Array(toCancel))
}
}
......@@ -814,8 +871,10 @@ public enum ASCollectionViewScrollPosition
@available(iOS 13.0, *)
public class AS_CollectionViewController: UIViewController
{
weak var coordinator: ASCollectionViewCoordinator? {
didSet {
weak var coordinator: ASCollectionViewCoordinator?
{
didSet
{
collectionView.coordinator = coordinator
}
}
......@@ -839,7 +898,14 @@ public class AS_CollectionViewController: UIViewController
public override func didMove(toParent parent: UIViewController?)
{
super.didMove(toParent: parent)
coordinator?.onMoveToParent()
if parent != nil
{
coordinator?.onMoveToParent()
}
else
{
coordinator?.onMoveFromParent()
}
}
public override func viewDidLoad()
......@@ -895,20 +961,22 @@ public class AS_CollectionViewController: UIViewController
}
}
@available(iOS 13.0, *)
public class AS_UICollectionView: UICollectionView {
weak var coordinator: ASCollectionViewCoordinator? = nil
public override func didMoveToWindow() {
public class AS_UICollectionView: UICollectionView
{
weak var coordinator: ASCollectionViewCoordinator?
public override func didMoveToWindow()
{
super.didMoveToWindow()
//Intended as a temporary workaround for a SwiftUI bug present in 13.3 -> the UIViewController is not moved to a parent when embedded in a list/scrollview
// Intended as a temporary workaround for a SwiftUI bug present in 13.3 -> the UIViewController is not moved to a parent when embedded in a list/scrollview
coordinator?.onMoveToParent()
}
}
// MARK: PUBLIC layout modifier functions
@available(iOS 13.0, *)
public extension ASCollectionView
{
......@@ -960,14 +1028,17 @@ public extension ASCollectionView
}
}
@available(iOS 13.0, *)
class ASCollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType: Hashable, ItemIdentifierType: Hashable
{
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
if animatingDifferences {
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil)
{
if animatingDifferences
{
super.apply(snapshot, animatingDifferences: true, completion: completion)
} else {
}
else
{
UIView.performWithoutAnimation {
super.apply(snapshot, animatingDifferences: false, completion: completion)
}
......
......@@ -4,20 +4,36 @@ import Foundation
import SwiftUI
@available(iOS 13.0, *)
class ASCollectionViewCell: UICollectionViewCell
protocol ASDataSourceConfigurableCell
{
func configureAsEmpty()
func configureHostingController<Content: View>(forItemID itemID: ASCollectionViewItemUniqueID, content: Content)
}
@available(iOS 13.0, *)
class ASCollectionViewCell: UICollectionViewCell, ASDataSourceConfigurableCell
{
var hostingController: ASHostingControllerProtocol?
{
didSet
{
let modifier = ASHostingControllerModifier(invalidateCellLayout: {
self.shouldInvalidateLayout = true
self.setNeedsLayout()
})
hostingController?.applyModifier(modifier)
guard hostingController !== oldValue else { return }
if let oldVC = oldValue?.viewController,
let oldParent = oldVC.parent
{
// Replace the old one if it was already visible (added to parent)
oldVC.removeFromParent()
oldVC.view.removeFromSuperview()
if let newVC = hostingController?.viewController
{
oldParent.addChild(newVC)
contentView.addSubview(newVC.view)
}
}
}
}
var selfSizingConfig: ASSelfSizingConfig = .init(selfSizeHorizontally: true, selfSizeVertically: true)
var maxSizeForSelfSizing: ASOptionalSize = .none
var invalidateLayout: (() -> Void)?
......@@ -25,6 +41,30 @@ class ASCollectionViewCell: UICollectionViewCell
private(set) var id: ASCollectionViewItemUniqueID?
func configureAsEmpty()
{
hostingController = nil
}
func configureHostingController<Content: View>(forItemID itemID: ASCollectionViewItemUniqueID, content: Content)
{
id = itemID
if let existingHC = hostingController as? ASHostingController<Content>
{
existingHC.setView(content)
}
else
{
let modifier = ASHostingControllerModifier(invalidateCellLayout: { [weak self] in
self?.shouldInvalidateLayout = true
self?.setNeedsLayout()
})
let newHC = ASHostingController<Content>(content, modifier: modifier)
hostingController = newHC
}
}
func setupFor(id: ASCollectionViewItemUniqueID, hostingController: ASHostingControllerProtocol?)
{
self.hostingController = hostingController
......@@ -34,20 +74,22 @@ class ASCollectionViewCell: UICollectionViewCell
func willAppear(in vc: UIViewController)
{
hostingController.map
{
if $0.viewController.parent != vc
{
if $0.viewController.parent != vc {
$0.viewController.removeFromParent()
vc.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView {
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
hostingController?.viewController.didMove(toParent: vc)
$0.viewController.removeFromParent()
vc.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView
{
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
hostingController?.viewController.didMove(toParent: vc)
}
}
......@@ -74,9 +116,6 @@ class ASCollectionViewCell: UICollectionViewCell
}
}
var selfSizeHorizontal: Bool = true
var selfSizeVertical: Bool = true
override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize
{
guard let hc = hostingController else
......@@ -86,8 +125,8 @@ class ASCollectionViewCell: UICollectionViewCell
let size = hc.sizeThatFits(
in: targetSize,
maxSize: maxSizeForSelfSizing,
selfSizeHorizontal: selfSizeHorizontal,
selfSizeVertical: selfSizeVertical)
selfSizeHorizontal: selfSizingConfig.selfSizeHorizontally,
selfSizeVertical: selfSizingConfig.selfSizeVertically)
return size
}
......@@ -108,7 +147,7 @@ class ASCollectionViewSupplementaryView: UICollectionReusableView
{
var hostingController: ASHostingControllerProtocol?
private(set) var id: Int?
var maxSizeForSelfSizing: ASOptionalSize = .none
func setupFor<Content: View>(id: Int, view: Content)
......@@ -116,8 +155,9 @@ class ASCollectionViewSupplementaryView: UICollectionReusableView
self.id = id
hostingController = ASHostingController<Content>(view)
}
func setupAsEmptyView() {
func setupAsEmptyView()
{
hostingController = nil
subviews.forEach { $0.removeFromSuperview() }
}
......@@ -132,11 +172,13 @@ class ASCollectionViewSupplementaryView: UICollectionReusableView
{
hostingController.map
{
if $0.viewController.parent != vc {
if $0.viewController.parent != vc
{
$0.viewController.removeFromParent()
vc?.addChild($0.viewController)
}
if $0.viewController.view.superview != self {
if $0.viewController.view.superview != self
{
$0.viewController.view.removeFromSuperview()
subviews.forEach { $0.removeFromSuperview() }
addSubview($0.viewController.view)
......@@ -165,14 +207,17 @@ class ASCollectionViewSupplementaryView: UICollectionReusableView
hostingController?.viewController.view.setNeedsLayout()
}
var selfSizingConfig: ASSelfSizingConfig = .init(selfSizeHorizontally: true, selfSizeVertically: true)
override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize
{
guard let hc = hostingController else { return CGSize(width: 1, height: 1) }
let size = hc.sizeThatFits(
in: targetSize,
maxSize: maxSizeForSelfSizing,
selfSizeHorizontal: true,
selfSizeVertical: true)
selfSizeHorizontal: selfSizingConfig.selfSizeHorizontally,
selfSizeVertical: selfSizingConfig.selfSizeVertically)
return size
}
......
......@@ -19,12 +19,7 @@ open class ASCollectionViewDelegate: NSObject, UICollectionViewDelegate, UIColle
coordinator?.typeErasedDataForItem(at: indexPath) as? T
}
open func collectionView(cellShouldSelfSizeHorizontallyForItemAt indexPath: IndexPath) -> Bool?
{
nil
}
open func collectionView(cellShouldSelfSizeVerticallyForItemAt indexPath: IndexPath) -> Bool?
open func collectionViewSelfSizingSettings(forContext: ASSelfSizingContext) -> ASSelfSizingConfig?
{
nil
}
......@@ -151,7 +146,8 @@ extension ASCollectionViewDelegate: UICollectionViewDragDelegate, UICollectionVi
@available(iOS 13.0, *)
extension ASCollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
self.coordinator?.collectionView(collectionView, contextMenuConfigurationForItemAt: indexPath, point: point)
public func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
{
coordinator?.collectionView(collectionView, contextMenuConfigurationForItemAt: indexPath, point: point)
}
}
......@@ -8,8 +8,7 @@ import UIKit
@available(iOS 13.0, *)
public protocol ASCollectionViewLayoutProtocol
{
var selfSizeVertically: Bool { get }
var selfSizeHorizontally: Bool { get }
var selfSizingConfig: ASSelfSizingConfig { get }
}
// MARK: Public Typealias for layout closures
......@@ -78,8 +77,8 @@ public struct ASCollectionLayout<SectionID: Hashable>
config.scrollDirection = scrollDirection
config.interSectionSpacing = interSectionSpacing
let sectionProvider: UICollectionViewCompositionalLayoutSectionProvider = { sectionIndex, layoutEnvironment -> NSCollectionLayoutSection in
guard let sectionID = coordinator.sectionID(fromSectionIndex: sectionIndex) else { return NSCollectionLayoutSection.placeholder(environment: layoutEnvironment, primaryScrollDirection: scrollDirection) }
let sectionProvider: UICollectionViewCompositionalLayoutSectionProvider = { [weak coordinator] sectionIndex, layoutEnvironment -> NSCollectionLayoutSection in
guard let sectionID = coordinator?.sectionID(fromSectionIndex: sectionIndex) else { return NSCollectionLayoutSection.placeholder(environment: layoutEnvironment, primaryScrollDirection: scrollDirection) }
return layoutClosure(sectionID).makeLayoutSection(environment: layoutEnvironment, primaryScrollDirection: scrollDirection)
}
......
......@@ -15,7 +15,7 @@ internal struct ASHostingControllerModifier: ViewModifier
}
@available(iOS 13.0, *)
internal protocol ASHostingControllerProtocol
internal protocol ASHostingControllerProtocol: AnyObject
{
var viewController: UIViewController { get }
func applyModifier(_ modifier: ASHostingControllerModifier)
......@@ -25,10 +25,11 @@ internal protocol ASHostingControllerProtocol
@available(iOS 13.0, *)
internal class ASHostingController<ViewType: View>: ASHostingControllerProtocol
{
init(_ view: ViewType)
init(_ view: ViewType, modifier: ASHostingControllerModifier = ASHostingControllerModifier())
{
hostedView = view
uiHostingController = .init(rootView: view.modifier(ASHostingControllerModifier()))
self.modifier = modifier
uiHostingController = .init(rootView: view.modifier(modifier))
}
let uiHostingController: UIHostingController<ModifiedContent<ViewType, ASHostingControllerModifier>>
......@@ -40,7 +41,7 @@ internal class ASHostingController<ViewType: View>: ASHostingControllerProtocol
}
var hostedView: ViewType
var modifier: ASHostingControllerModifier = ASHostingControllerModifier()
var modifier: ASHostingControllerModifier
{
didSet
{
......@@ -63,8 +64,8 @@ internal class ASHostingController<ViewType: View>: ASHostingControllerProtocol
{
let fittingSize = CGSize(
width: selfSizeHorizontal ? .infinity : size.width,
height: selfSizeVertical ? .infinity : size.height
).applyMaxSize(maxSize)
height: selfSizeVertical ? .infinity : size.height).applyMaxSize(maxSize)
// Find the desired size
var desiredSize = uiHostingController.sizeThatFits(in: fittingSize)
......
......@@ -75,12 +75,11 @@ public struct ASSection<SectionID: Hashable>
data: data,
dataIDKeyPath: dataIDKeyPath,
container: container,
onCellEvent: onCellEvent,
content: contentBuilder, onCellEvent: onCellEvent,
onDragDrop: onDragDropEvent,
itemProvider: itemProvider,
onSwipeToDelete: onSwipeToDelete,
contextMenuProvider: contextMenuProvider,
content: contentBuilder)
contextMenuProvider: contextMenuProvider)
}
public init<DataCollection: RandomAccessCollection, DataID: Hashable, Content: View>(
......@@ -197,7 +196,8 @@ public extension ASCollectionViewSection
content: { staticContent, _ in staticContent.view })
}
init(id: SectionID, @ViewArrayBuilder content: () -> ViewArrayBuilder.Wrapper) {
init(id: SectionID, @ViewArrayBuilder content: () -> ViewArrayBuilder.Wrapper)
{
self.init(id: id, container: { $0 }, content: content)
}
......@@ -223,6 +223,19 @@ public extension ASCollectionViewSection
}
}
// MARK: Self-sizing config
@available(iOS 13.0, *)
public extension ASSection
{
func selfSizingConfig(config: SelfSizingConfig?) -> Self
{
var section = self
section.dataSource.setSelfSizingConfig(config: config)
return section
}
}
// MARK: IDENTIFIABLE DATA SECTION
@available(iOS 13.0, *)
......@@ -230,7 +243,6 @@ public extension ASCollectionViewSection
{
/**
Initializes a section with identifiable data
- Parameters:
- id: The id for this section
- data: The data to display in the section. This initialiser expects data that conforms to 'Identifiable'
......@@ -248,7 +260,7 @@ public extension ASCollectionViewSection
onSwipeToDelete: OnSwipeToDelete<DataCollection.Element>? = nil,
contextMenuProvider: ContextMenuProvider<DataCollection.Element>? = nil,
@ViewBuilder contentBuilder: @escaping ((DataCollection.Element, CellContext) -> Content))
where DataCollection.Index == Int, DataCollection.Element: Identifiable
where DataCollection.Index == Int, DataCollection.Element: Identifiable
{
self.init(id: id, data: data, dataID: \.id, container: container, onCellEvent: onCellEvent, onDragDropEvent: onDragDropEvent, itemProvider: itemProvider, onSwipeToDelete: onSwipeToDelete, contextMenuProvider: contextMenuProvider, contentBuilder: contentBuilder)
}
......@@ -262,7 +274,7 @@ public extension ASCollectionViewSection
onSwipeToDelete: OnSwipeToDelete<DataCollection.Element>? = nil,
contextMenuProvider: ContextMenuProvider<DataCollection.Element>? = nil,
@ViewBuilder contentBuilder: @escaping ((DataCollection.Element, CellContext) -> Content))
where DataCollection.Index == Int, DataCollection.Element: Identifiable
where DataCollection.Index == Int, DataCollection.Element: Identifiable
{
self.init(id: id, data: data, container: { $0 }, onCellEvent: onCellEvent, onDragDropEvent: onDragDropEvent, itemProvider: itemProvider, onSwipeToDelete: onSwipeToDelete, contextMenuProvider: contextMenuProvider, contentBuilder: contentBuilder)
}
......
......@@ -8,7 +8,7 @@ internal protocol ASSectionDataSourceProtocol
{
func getIndexPaths(withSectionIndex sectionIndex: Int) -> [IndexPath]
func getUniqueItemIDs<SectionID: Hashable>(withSectionID sectionID: SectionID) -> [ASCollectionViewItemUniqueID]
func configureHostingController(reusingController: ASHostingControllerProtocol?, forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool) -> ASHostingControllerProtocol?
func configureCell(_ cell: ASDataSourceConfigurableCell, forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool)
func getTypeErasedData(for indexPath: IndexPath) -> Any?
func onAppear(_ indexPath: IndexPath)
func onDisappear(_ indexPath: IndexPath)
......@@ -20,8 +20,16 @@ internal protocol ASSectionDataSourceProtocol
func supportsDelete(at indexPath: IndexPath) -> Bool
func onDelete(indexPath: IndexPath, completionHandler: (Bool) -> Void)
func getContextMenu(for indexPath: IndexPath) -> UIContextMenuConfiguration?
func getSelfSizingSettings(context: ASSelfSizingContext) -> ASSelfSizingConfig?
var dragEnabled: Bool { get }
var dropEnabled: Bool { get }
mutating func setSelfSizingConfig(config: SelfSizingConfig?)
var estimatedRowHeight: CGFloat? { get set }
var estimatedHeaderHeight: CGFloat? { get set }
var estimatedFooterHeight: CGFloat? { get set }
}
@available(iOS 13.0, *)
......@@ -62,6 +70,9 @@ public typealias OnSwipeToDelete<Data> = ((Data, _ completionHandler: (Bool) ->
@available(iOS 13.0, *)
public typealias ContextMenuProvider<Data> = ((_ item: Data) -> UIContextMenuConfiguration?)
@available(iOS 13.0, *)
public typealias SelfSizingConfig = ((_ context: ASSelfSizingContext) -> ASSelfSizingConfig?)
@available(iOS 13.0, *)
public struct CellContext
{
......@@ -77,12 +88,20 @@ internal struct ASSectionDataSource<DataCollection: RandomAccessCollection, Data
var data: DataCollection
var dataIDKeyPath: KeyPath<Data, DataID>
var container: (Content) -> Container
var onCellEvent: OnCellEvent<Data>?
var onDragDrop: OnDragDrop<Data>?
var itemProvider: ItemProvider<Data>?
var onSwipeToDelete: OnSwipeToDelete<Data>?
var contextMenuProvider: ContextMenuProvider<Data>?
var content: (Data, CellContext) -> Content
var content: (DataCollection.Element, CellContext) -> Content
var supplementaryViews: [String: AnyView] = [:]
var onCellEvent: OnCellEvent<DataCollection.Element>?
var onDragDrop: OnDragDrop<DataCollection.Element>?
var itemProvider: ItemProvider<DataCollection.Element>?
var onSwipeToDelete: OnSwipeToDelete<DataCollection.Element>?
var contextMenuProvider: ContextMenuProvider<DataCollection.Element>?
var selfSizingConfig: SelfSizingConfig?
// Only relevant for ASTableView
public var estimatedRowHeight: CGFloat?
public var estimatedHeaderHeight: CGFloat?
public var estimatedFooterHeight: CGFloat?
var dragEnabled: Bool { onDragDrop != nil }
var dropEnabled: Bool { onDragDrop != nil }
......@@ -95,22 +114,17 @@ internal struct ASSectionDataSource<DataCollection: RandomAccessCollection, Data
isLastInSection: data.last?[keyPath: dataIDKeyPath].hashValue == itemID.itemIDHash)
}
func configureHostingController(reusingController: ASHostingControllerProtocol? = nil, forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool) -> ASHostingControllerProtocol?
func configureCell(_ cell: ASDataSourceConfigurableCell, forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool)
{
guard let item = data.first(where: { $0[keyPath: dataIDKeyPath].hashValue == itemID.itemIDHash }) else { return nil }
let view = content(item, cellContext(forItemID: itemID, isSelected: isSelected))
let containedView = container(view)
if let existingHC = reusingController as? ASHostingController<Container>
{
existingHC.setView(containedView)
return existingHC
}
else
guard let item = data.first(where: { $0[keyPath: dataIDKeyPath].hashValue == itemID.itemIDHash }) else
{
let newHC = ASHostingController<Container>(containedView)
return newHC
cell.configureAsEmpty()
return
}
let view = content(item, cellContext(forItemID: itemID, isSelected: isSelected))
let content = container(view)
cell.configureHostingController(forItemID: itemID, content: content)
}
func getTypeErasedData(for indexPath: IndexPath) -> Any?
......@@ -201,14 +215,30 @@ internal struct ASSectionDataSource<DataCollection: RandomAccessCollection, Data
}
onDragDrop?(.onAddItems(items: dataItems, atIndexPath: indexPath))
}
func getContextMenu(for indexPath: IndexPath) -> UIContextMenuConfiguration?
{
guard
let menuProvider = contextMenuProvider,
let item = data[safe: indexPath.item]
else { return nil }
else { return nil }
return menuProvider(item)
}
func getSelfSizingSettings(context: ASSelfSizingContext) -> ASSelfSizingConfig?
{
selfSizingConfig?(context)
}
}
// MARK: SELF SIZING MODIFIERS - INTERNAL
@available(iOS 13.0, *)
internal extension ASSectionDataSource
{
mutating func setSelfSizingConfig(config: SelfSizingConfig?)
{
selfSizingConfig = config
}
}
......@@ -56,8 +56,7 @@ extension ASTableView where SectionID == Int
{
ASTableView(
style: .plain,
sections: [ASTableViewSection(id: 0, content: staticContent)]
)
sections: [ASTableViewSection(id: 0, content: staticContent)])
}
}
......@@ -80,7 +79,7 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
// MARK: Environment variables
@Environment(\.tableViewSeparatorsEnabled) private var separatorsEnabled
@Environment(\.tableViewOnPullToRefresh) private var onPullToRefresh
@Environment(\.onPullToRefresh) private var onPullToRefresh
@Environment(\.tableViewOnReachedBottom) private var onReachedBottom
@Environment(\.scrollIndicatorsEnabled) private var scrollIndicatorsEnabled
@Environment(\.contentInsets) private var contentInsets
......@@ -152,7 +151,7 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
public class Coordinator: NSObject, ASTableViewCoordinator, UITableViewDelegate, UITableViewDataSourcePrefetching
{
var parent: ASTableView
var tableViewController: AS_TableViewController?
weak var tableViewController: AS_TableViewController?
var dataSource: ASTableViewDiffableDataSource<SectionID, ASCollectionViewItemUniqueID>?
......@@ -163,8 +162,6 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
private var hasDoneInitialSetup = false
var hostingControllerCache = ASFIFODictionary<ASCollectionViewItemUniqueID, ASHostingControllerProtocol>()
typealias Cell = ASTableViewCell
init(_ parent: ASTableView)
......@@ -183,14 +180,6 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
.first(where: { $0.id.hashValue == itemID.sectionIDHash })
}
@discardableResult
func configureHostingController(forItemID itemID: ASCollectionViewItemUniqueID, isSelected: Bool) -> ASHostingControllerProtocol?
{
let controller = section(forItemID: itemID)?.dataSource.configureHostingController(reusingController: hostingControllerCache[itemID], forItemID: itemID, isSelected: isSelected)
hostingControllerCache[itemID] = controller
return controller
}
func setupDataSource(forTableView tv: UITableView)
{
tv.delegate = self
......@@ -199,19 +188,28 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
tv.register(ASTableViewSupplementaryView.self, forHeaderFooterViewReuseIdentifier: supplementaryReuseID)
dataSource = .init(tableView: tv)
{ (tableView, indexPath, itemID) -> UITableViewCell? in
{ [weak self] (tableView, indexPath, itemID) -> UITableViewCell? in
guard let self = self else { return nil }
let isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false
guard
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellReuseID, for: indexPath) as? Cell,
let hostController = self.configureHostingController(forItemID: itemID, isSelected: isSelected)
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellReuseID, for: indexPath) as? Cell
else { return nil }
cell.invalidateLayout = {
tv.beginUpdates()
tv.endUpdates()
// Cell layout invalidation callback
cell.invalidateLayout = { [weak tv] in
tv?.beginUpdates()
tv?.endUpdates()
}
cell.setupFor(
id: itemID,
hostingController: hostController)
// Self Sizing Settings
let selfSizingContext = ASSelfSizingContext(cellType: .content, indexPath: indexPath)
cell.selfSizingConfig =
self.parent.sections[safe: indexPath.section]?.dataSource.getSelfSizingSettings(context: selfSizingContext)
?? ASSelfSizingConfig(selfSizeHorizontally: false, selfSizeVertically: true)
// Configure cell
self.section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: isSelected)
return cell
}
dataSource?.defaultRowAnimation = .fade
......@@ -240,14 +238,15 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
let itemID = cell.id
else { return }
self.configureHostingController(forItemID: itemID, isSelected: cell.isSelected)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
}
populateDataSource(animated: animated)
updateSelectionBindings(tv)
}
func onMoveToParent(_ parentController: AS_TableViewController)
func onMoveToParent(tableViewController: AS_TableViewController)
{
if !hasDoneInitialSetup
{
......@@ -257,10 +256,15 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
populateDataSource(animated: false)
// Check if reached bottom already
checkIfReachedBottom(parentController.tableView)
checkIfReachedBottom(tableViewController.tableView)
}
}
func onMoveFromParent()
{
hasDoneInitialSetup = false
}
func configureRefreshControl(for tv: UITableView)
{
guard parent.onPullToRefresh != nil else
......@@ -374,7 +378,8 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
let itemID = cell.id
else { return }
updateSelectionBindings(tableView)
configureHostingController(forItemID: itemID, isSelected: true)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
......@@ -384,7 +389,8 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
let itemID = cell.id
else { return }
updateSelectionBindings(tableView)
configureHostingController(forItemID: itemID, isSelected: false)
// Configure cell
section(forItemID: itemID)?.dataSource.configureCell(cell, forItemID: itemID, isSelected: cell.isSelected)
}
func updateSelectionBindings(_ tableView: UITableView)
......@@ -447,6 +453,13 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
else { return nil }
if let supplementaryView = parent.sections[safe: section]?.supplementary(ofKind: UICollectionView.elementKindSectionHeader)
{
// Self Sizing Settings
let selfSizingContext = ASSelfSizingContext(cellType: .supplementary(UICollectionView.elementKindSectionHeader), indexPath: IndexPath(row: 0, section: section))
reusableView.selfSizingConfig =
parent.sections[safe: section]?.dataSource.getSelfSizingSettings(context: selfSizingContext)
?? ASSelfSizingConfig(selfSizeHorizontally: false, selfSizeVertically: true)
// Cell Content Setup
reusableView.setupFor(
id: section,
view: supplementaryView)
......@@ -460,6 +473,13 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
else { return nil }
if let supplementaryView = parent.sections[safe: section]?.supplementary(ofKind: UICollectionView.elementKindSectionFooter)
{
// Self Sizing Settings
let selfSizingContext = ASSelfSizingContext(cellType: .supplementary(UICollectionView.elementKindSectionFooter), indexPath: IndexPath(row: 0, section: section))
reusableView.selfSizingConfig =
parent.sections[safe: section]?.dataSource.getSelfSizingSettings(context: selfSizingContext)
?? ASSelfSizingConfig(selfSizeHorizontally: false, selfSizeVertically: true)
// Cell Content Setup
reusableView.setupFor(
id: section,
view: supplementaryView)
......@@ -494,7 +514,8 @@ public struct ASTableView<SectionID: Hashable>: UIViewControllerRepresentable
@available(iOS 13.0, *)
protocol ASTableViewCoordinator: AnyObject
{
func onMoveToParent(_ parentController: AS_TableViewController)
func onMoveToParent(tableViewController: AS_TableViewController)
func onMoveFromParent()
}
// MARK: ASTableView specific header modifiers
......@@ -557,7 +578,14 @@ public class AS_TableViewController: UIViewController
public override func didMove(toParent parent: UIViewController?)
{
super.didMove(toParent: parent)
coordinator?.onMoveToParent(self)
if parent != nil
{
coordinator?.onMoveToParent(tableViewController: self)
}
else
{
coordinator?.onMoveFromParent()
}
}
}
......@@ -568,11 +596,15 @@ class ASTableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>:
{
true
}
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
if animatingDifferences {
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil)
{
if animatingDifferences
{
super.apply(snapshot, animatingDifferences: true, completion: completion)
} else {
}
else
{
UIView.performWithoutAnimation {
super.apply(snapshot, animatingDifferences: false, completion: completion)
}
......
......@@ -4,20 +4,29 @@ import Foundation
import SwiftUI
@available(iOS 13.0, *)
class ASTableViewCell: UITableViewCell
class ASTableViewCell: UITableViewCell, ASDataSourceConfigurableCell
{
var hostingController: ASHostingControllerProtocol?
{
didSet
{
let modifier = ASHostingControllerModifier(invalidateCellLayout: {
self.shouldInvalidateLayout = true
self.setNeedsLayout()
})
hostingController?.applyModifier(modifier)
guard hostingController !== oldValue else { return }
if let oldVC = oldValue?.viewController,
let oldParent = oldVC.parent
{
// Replace the old one if it was already visible (added to parent)
oldVC.removeFromParent()
oldVC.view.removeFromSuperview()
if let newVC = hostingController?.viewController
{
oldParent.addChild(newVC)
contentView.addSubview(newVC.view)
}
}
}
}
var selfSizingConfig: ASSelfSizingConfig = .init(selfSizeHorizontally: false, selfSizeVertically: true)
var maxSizeForSelfSizing: ASOptionalSize = .none
var invalidateLayout: (() -> Void)?
......@@ -25,30 +34,49 @@ class ASTableViewCell: UITableViewCell
private(set) var id: ASCollectionViewItemUniqueID?
func setupFor(id: ASCollectionViewItemUniqueID, hostingController: ASHostingControllerProtocol?)
func configureAsEmpty()
{
self.hostingController = hostingController
self.id = id
selectionStyle = .none
hostingController = nil
}
func configureHostingController<Content: View>(forItemID itemID: ASCollectionViewItemUniqueID, content: Content)
{
id = itemID
if let existingHC = hostingController as? ASHostingController<Content>
{
existingHC.setView(content)
}
else
{
let modifier = ASHostingControllerModifier(invalidateCellLayout: { [weak self] in
self?.shouldInvalidateLayout = true
self?.setNeedsLayout()
})
let newHC = ASHostingController<Content>(content, modifier: modifier)
hostingController = newHC
}
}
func willAppear(in vc: UIViewController?)
{
hostingController.map
{
if $0.viewController.parent != vc
{
if $0.viewController.parent != vc {
$0.viewController.removeFromParent()
vc?.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView {
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
vc.map { hostingController?.viewController.didMove(toParent: $0) }
$0.viewController.removeFromParent()
vc?.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView
{
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
vc.map { hostingController?.viewController.didMove(toParent: $0) }
}
}
......@@ -60,6 +88,7 @@ class ASTableViewCell: UITableViewCell
override func prepareForReuse()
{
hostingController = nil
isSelected = false
}
override func layoutSubviews()
......@@ -73,8 +102,6 @@ class ASTableViewCell: UITableViewCell
}
}
var selfSizeVertical: Bool = true
override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize
{
guard let hc = hostingController else { return .zero }
......@@ -82,7 +109,7 @@ class ASTableViewCell: UITableViewCell
in: targetSize,
maxSize: maxSizeForSelfSizing,
selfSizeHorizontal: false,
selfSizeVertical: selfSizeVertical)
selfSizeVertical: selfSizingConfig.selfSizeVertically)
return size
}
......@@ -97,7 +124,8 @@ class ASTableViewSupplementaryView: UITableViewHeaderFooterView
{
var hostingController: ASHostingControllerProtocol?
private(set) var id: Int?
var selfSizingConfig: ASSelfSizingConfig = .init(selfSizeHorizontally: false, selfSizeVertically: true)
var maxSizeForSelfSizing: ASOptionalSize = .none
override init(reuseIdentifier: String?)
......@@ -134,20 +162,22 @@ class ASTableViewSupplementaryView: UITableViewHeaderFooterView
func willAppear(in vc: UIViewController?)
{
hostingController.map
{
if $0.viewController.parent != vc
{
if $0.viewController.parent != vc {
$0.viewController.removeFromParent()
vc?.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView {
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
vc.map { hostingController?.viewController.didMove(toParent: $0) }
$0.viewController.removeFromParent()
vc?.addChild($0.viewController)
}
if $0.viewController.view.superview != contentView
{
$0.viewController.view.removeFromSuperview()
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview($0.viewController.view)
}
setNeedsLayout()
vc.map { hostingController?.viewController.didMove(toParent: $0) }
}
}
......@@ -173,8 +203,8 @@ class ASTableViewSupplementaryView: UITableViewHeaderFooterView
let size = hc.sizeThatFits(
in: targetSize,
maxSize: maxSizeForSelfSizing,
selfSizeHorizontal: true,
selfSizeVertical: true)
selfSizeHorizontal: false,
selfSizeVertical: selfSizingConfig.selfSizeVertically)
return size
}
......
......@@ -18,6 +18,7 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
{
case fixed(Int)
case adaptive(minWidth: CGFloat)
case perSection((Int) -> ColumnCount)
}
public var numberOfColumns: ColumnCount = .adaptive(minWidth: 150)
......@@ -49,14 +50,13 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
}
}
public var selfSizeVertically: Bool
public var selfSizingConfig: ASSelfSizingConfig
{
if hasDelegate { return false }
else { return true } // No delegate, use autosizing
ASSelfSizingConfig(
selfSizeHorizontally: false,
selfSizeVertically: hasDelegate ? false : true)
}
public let selfSizeHorizontally = false
private var cachedHeaderHeight: [Int: CGFloat] = [:]
private var cachedHeight: [IndexPath: CGFloat] = [:]
private var cachedSectionHeight: [Int: CGFloat] = [:]
......@@ -77,20 +77,24 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
CGSize(width: contentWidth, height: contentHeight)
}
var calculatedNumberOfColumns: Int
func calculateNumberOfColumns(sectionIndex: Int, setting: ColumnCount) -> Int
{
switch numberOfColumns
switch setting
{
case let .fixed(num):
return num
case let .adaptive(minWidth):
return Int(floor((contentWidth + columnSpacing) / (minWidth + columnSpacing)))
case let .perSection(callback):
let resolved = callback(sectionIndex)
return calculateNumberOfColumns(sectionIndex: sectionIndex, setting: resolved)
}
}
var columnWidth: CGFloat
func calculateColumnWidth(sectionIndex: Int) -> CGFloat
{
(contentWidth - (columnSpacing * CGFloat(calculatedNumberOfColumns - 1))) / CGFloat(calculatedNumberOfColumns)
let sectionColumnCount = calculateNumberOfColumns(sectionIndex: sectionIndex, setting: numberOfColumns)
return (contentWidth - (columnSpacing * CGFloat(sectionColumnCount - 1))) / CGFloat(sectionColumnCount)
}
var hasDelegate: Bool
......@@ -104,19 +108,19 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
{
return delegate.heightForCell(
at: indexPath,
context: ASWaterfallLayout.CellLayoutContext(width: columnWidth))
context: ASWaterfallLayout.CellLayoutContext(width: calculateColumnWidth(sectionIndex: indexPath.section)))
}
return cachedHeight[indexPath] ?? estimatedItemHeight
}
func getHeightForHeader(sectionIndex: Int) -> CGFloat
{
let delegate = (collectionView?.delegate as? ASWaterfallLayoutDelegate)
return
delegate?.heightForHeader(sectionIndex: sectionIndex)
?? cachedHeaderHeight[sectionIndex]
?? estimatedHeaderHeight
?? cachedHeaderHeight[sectionIndex]
?? estimatedHeaderHeight
}
public override func prepare()
......@@ -130,11 +134,11 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
for section in sections
{
let sectionMinY = (0 ..< section).reduce(into: collectionView.adjustedContentInset.top) { $0 += cachedSectionHeight[$1] ?? 0 }
var columnHeights: [CGFloat] = .init(repeating: 0, count: calculatedNumberOfColumns)
var columnHeights: [CGFloat] = .init(repeating: 0, count: calculateNumberOfColumns(sectionIndex: section, setting: numberOfColumns))
let headerHeight = getHeightForHeader(sectionIndex: section)
//Set header attributes
// Set header attributes
let headerIndexPath = IndexPath(item: -1, section: section)
let headerAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: headerIndexPath)
headerAttributes.frame = CGRect(
......@@ -143,9 +147,11 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
width: contentWidth,
height: headerHeight)
cachedAttributes[headerIndexPath] = headerAttributes
let sectionContentMinY = sectionMinY + headerHeight + itemSpacing
let sectionColumnWidth = calculateColumnWidth(sectionIndex: section)
for indexPath in collectionView.allIndexPaths(inSection: section)
{
let targetColumn = columnHeights.indexOfMin() ?? 0
......@@ -153,8 +159,8 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
let minY = sectionContentMinY + columnHeights[targetColumn]
let sizeY = getHeight(for: indexPath)
let minX = (columnWidth + columnSpacing) * CGFloat(targetColumn)
let sizeX = columnWidth
let minX = (sectionColumnWidth + columnSpacing) * CGFloat(targetColumn)
let sizeX = sectionColumnWidth
// Set cached attributes
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
......@@ -215,9 +221,11 @@ public class ASWaterfallLayout: UICollectionViewLayout, ASCollectionViewLayoutPr
{
cachedAttributes[indexPath]
}
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
switch elementKind {
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
{
switch elementKind
{
case UICollectionView.elementKindSectionHeader:
return cachedAttributes[IndexPath(item: -1, section: indexPath.section)]
default:
......
......@@ -3,7 +3,8 @@
import Foundation
import SwiftUI
//MARK: PUBLIC MODIFIERS
// MARK: PUBLIC MODIFIERS
@available(iOS 13.0, *)
public extension View
{
......@@ -12,83 +13,89 @@ public extension View
{
environment(\.scrollIndicatorsEnabled, enabled)
}
/// Set insets for the ASCollectionView/ASTableView
func collectionViewContentInsets(_ insets: UIEdgeInsets) -> some View
{
environment(\.contentInsets, insets)
}
/// Set whether the ASTableView should show separators
func tableViewSeparatorsEnabled(_ enabled: Bool) -> some View
{
environment(\.tableViewSeparatorsEnabled, enabled)
}
/// Set a closure that is called when the tableView is pulled to refresh
func tableViewOnPullToRefresh(_ onPullToRefresh: ((_ endRefreshing: @escaping (() -> Void)) -> Void)?) -> some View
/// Set a closure that is called when the tableView or the collectionView is pulled to refresh
func onPullToRefresh(_ onPullToRefresh: ((_ endRefreshing: @escaping (() -> Void)) -> Void)?) -> some View
{
environment(\.tableViewOnPullToRefresh, onPullToRefresh)
environment(\.onPullToRefresh, onPullToRefresh)
}
/// Set a closure that is called whenever the tableView is scrolled to the bottom.
/// This is useful to enable loading more data when scrolling to bottom
func tableViewOnReachedBottom(_ onReachedBottom: @escaping (() -> Void)) -> some View
{
environment(\.tableViewOnReachedBottom, onReachedBottom)
}
/// Set a closure that is called whenever the collectionView is scrolled to a boundary. eg. the bottom.
/// This is useful to enable loading more data when scrolling to bottom
func collectionViewOnReachedBoundary(_ onReachedBoundary: @escaping ((Boundary) -> Void)) -> some View
{
environment(\.collectionViewOnReachedBoundary, onReachedBoundary)
}
/// Set whether the ASCollectionView should always allow horizontal bounce
func collectionViewAlwaysBounceHorizontal(_ alwaysBounce: Bool = true) -> some View
{
environment(\.alwaysBounceHorizontal, alwaysBounce)
}
/// Set whether the ASCollectionView/ASTableView should always allow horizontal bounce
func collectionViewAlwaysBounceVertical(_ alwaysBounce: Bool = true) -> some View
{
environment(\.alwaysBounceVertical, alwaysBounce)
}
/// Set an initial scroll position for the ASCollectionView
func collectionViewInitialScrollPosition(_ scrollPosition: ASCollectionViewScrollPosition?) -> some View
{
environment(\.initialScrollPosition, scrollPosition)
}
/// Set whether the ASCollectionView/ASTableView should animate on data refresh
func collectionViewAnimateOnDataRefresh(_ animateOnDataRefresh: Bool = true) -> some View
{
environment(\.animateOnDataRefresh, animateOnDataRefresh)
}
/// Set whether the ASCollectionView should attempt to maintain scroll position on orientation change, default is true
func collectionViewAttemptToMaintainScrollPositionOnOrientationChange(_ attemptToMaintainScrollPositionOnOrientationChange: Bool = true) -> some View
{
environment(\.attemptToMaintainScrollPositionOnOrientationChange, attemptToMaintainScrollPositionOnOrientationChange)
}
/// Set whether the ASCollectionView should allow a cell's width to exceed the contentSize.width of the collectionView, default is true.
func collectionViewAllowCellWidthToExceedCollectionContentSize(_ allowCellWidthToExceedCollectionContentSize: Bool) -> some View
{
environment(\.allowCellWidthToExceedCollectionContentSize, allowCellWidthToExceedCollectionContentSize)
}
/// Set whether the ASCollectionView should allow a cell's height to exceed the contentSize.height of the collectionView, default is true.
func collectionViewAllowCellHeightToExceedCollectionContentSize(_ allowCellHeightToExceedCollectionContentSize: Bool) -> some View
{
environment(\.allowCellHeightToExceedCollectionContentSize, allowCellHeightToExceedCollectionContentSize)
}
func animateOnDataRefresh(_ animateOnDataRefresh: Bool = true) -> some View
{
environment(\.animateOnDataRefresh, animateOnDataRefresh)
}
}
//MARK: Internal Key Definitions
// MARK: Internal Key Definitions
@available(iOS 13.0, *)
struct EnvironmentKeyInvalidateCellLayout: EnvironmentKey
{
......@@ -114,7 +121,7 @@ struct EnvironmentKeyASTableViewSeparatorsEnabled: EnvironmentKey
}
@available(iOS 13.0, *)
struct EnvironmentKeyASTableViewOnPullToRefresh: EnvironmentKey
struct EnvironmentKeyASViewOnPullToRefresh: EnvironmentKey
{
static let defaultValue: (((_ endRefreshing: @escaping (() -> Void)) -> Void)?) = nil
}
......@@ -173,7 +180,8 @@ struct EnvironmentKeyASAllowCellHeightToExceedCollectionContentSize: Environment
static let defaultValue: Bool = true
}
//MARK: Internal Helpers
// MARK: Internal Helpers
@available(iOS 13.0, *)
public extension EnvironmentValues
{
......@@ -201,10 +209,10 @@ public extension EnvironmentValues
set { self[EnvironmentKeyASTableViewSeparatorsEnabled.self] = newValue }
}
var tableViewOnPullToRefresh: ((_ endRefreshing: @escaping (() -> Void)) -> Void)?
var onPullToRefresh: ((_ endRefreshing: @escaping (() -> Void)) -> Void)?
{
get { self[EnvironmentKeyASTableViewOnPullToRefresh.self] }
set { self[EnvironmentKeyASTableViewOnPullToRefresh.self] = newValue }
get { self[EnvironmentKeyASViewOnPullToRefresh.self] }
set { self[EnvironmentKeyASViewOnPullToRefresh.self] = newValue }
}
var tableViewOnReachedBottom: () -> Void
......@@ -242,19 +250,19 @@ public extension EnvironmentValues
get { self[EnvironmentKeyASAnimateOnDataRefresh.self] }
set { self[EnvironmentKeyASAnimateOnDataRefresh.self] = newValue }
}
var attemptToMaintainScrollPositionOnOrientationChange: Bool
{
get { self[EnvironmentKeyASMaintainScrollPositionOnOrientationChange.self] }
set { self[EnvironmentKeyASMaintainScrollPositionOnOrientationChange.self] = newValue }
}
var allowCellWidthToExceedCollectionContentSize: Bool
{
get { self[EnvironmentKeyASAllowCellWidthToExceedCollectionContentSize.self] }
set { self[EnvironmentKeyASAllowCellWidthToExceedCollectionContentSize.self] = newValue }
}
var allowCellHeightToExceedCollectionContentSize: Bool
{
get { self[EnvironmentKeyASAllowCellHeightToExceedCollectionContentSize.self] }
......
......@@ -3,22 +3,25 @@
import Foundation
import SwiftUI
@available (iOS 13.0, *)
public protocol Nestable {
@available(iOS 13.0, *)
public protocol Nestable
{
associatedtype T
func asArray() -> [T]
}
@available(iOS 13.0, *)
extension ASSection: Nestable {
public func asArray() -> [ASSection] {
public func asArray() -> [ASSection]
{
[self]
}
}
@available(iOS 13.0, *)
extension Array: Nestable {
public func asArray() -> Self {
public func asArray() -> Self
{
self
}
}
......@@ -44,52 +47,52 @@ public struct SectionArrayBuilder<SectionID> where SectionID: Hashable
{
item.map { $0.asArray() } ?? []
}
public static func buildBlock<C0: Nestable>(_ section: C0) -> Output where C0.T == Section
{
section.asArray()
}
public static func buildBlock<C0: Nestable, C1: Nestable>(_ item0: C0, _ item1: C1) -> Output where C0.T == Section, C1.T == Section
{
[item0.asArray(), item1.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2) -> Output where C0.T == Section, C1.T == Section, C2.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable, C5: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4, _ item5: C5) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section, C5.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray(), item5.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable, C5: Nestable, C6: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4, _ item5: C5, _ item6: C6) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section, C5.T == Section, C6.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray(), item5.asArray(), item6.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable, C5: Nestable, C6: Nestable, C7: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4, _ item5: C5, _ item6: C6, _ item7: C7) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section, C5.T == Section, C6.T == Section, C7.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray(), item5.asArray(), item6.asArray(), item7.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable, C5: Nestable, C6: Nestable, C7: Nestable, C8: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4, _ item5: C5, _ item6: C6, _ item7: C7, _ item8: C8) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section, C5.T == Section, C6.T == Section, C7.T == Section, C8.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray(), item5.asArray(), item6.asArray(), item7.asArray(), item8.asArray()].flatMap { $0 }
}
public static func buildBlock<C0: Nestable, C1: Nestable, C2: Nestable, C3: Nestable, C4: Nestable, C5: Nestable, C6: Nestable, C7: Nestable, C8: Nestable, C9: Nestable>(_ item0: C0, _ item1: C1, _ item2: C2, _ item3: C3, _ item4: C4, _ item5: C5, _ item6: C6, _ item7: C7, _ item8: C8, _ item9: C9) -> Output where C0.T == Section, C1.T == Section, C2.T == Section, C3.T == Section, C4.T == Section, C5.T == Section, C6.T == Section, C7.T == Section, C8.T == Section, C9.T == Section
{
[item0.asArray(), item1.asArray(), item2.asArray(), item3.asArray(), item4.asArray(), item5.asArray(), item6.asArray(), item7.asArray(), item8.asArray(), item9.asArray()].flatMap { $0 }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment