Skip to content

Commit 56e71ac

Browse files
authored
Fix a broken panel layout with a compositional collection view (#634)
* Fix #628 * Add a new sample to test UICollectionView using a compositional layout
1 parent f45b6aa commit 56e71ac

File tree

8 files changed

+267
-29
lines changed

8 files changed

+267
-29
lines changed

Examples/Samples/Samples.xcodeproj/project.pbxproj

+17-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24925FC53C100A26F43 /* DebugTextViewController.swift */; };
1919
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E25125FC541700A26F43 /* NestedScrollViewController.swift */; };
2020
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54496C58263A7E5A0031E0C8 /* UseCaseController.swift */; };
21-
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */; };
21+
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */; };
2222
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9ED21511E6300CA77B8 /* AppDelegate.swift */; };
2323
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* MainViewController.swift */; };
2424
545DB9F321511E6300CA77B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F121511E6300CA77B8 /* Main.storyboard */; };
@@ -30,6 +30,7 @@
3030
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3131
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
3232
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */; };
33+
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */; };
3334
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* PanelLayouts.swift */; };
3435
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD364263A765F006A36EA /* PagePanelController.swift */; };
3536
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */; };
@@ -63,7 +64,7 @@
6364
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTextViewController.swift; sourceTree = "<group>"; };
6465
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScrollViewController.swift; sourceTree = "<group>"; };
6566
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseController.swift; sourceTree = "<group>"; };
66-
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveLayoutTestViewController.swift; sourceTree = "<group>"; };
67+
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
6768
545DB9EA21511E6300CA77B8 /* Samples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samples.app; sourceTree = BUILT_PRODUCTS_DIR; };
6869
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6970
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
@@ -76,6 +77,7 @@
7677
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7778
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
7879
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupplementaryViews.swift; sourceTree = "<group>"; };
80+
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
7981
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelLayouts.swift; sourceTree = "<group>"; };
8082
54EAD364263A765F006A36EA /* PagePanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePanelController.swift; sourceTree = "<group>"; };
8183
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugListCollectionViewController.swift; sourceTree = "<group>"; };
@@ -99,18 +101,17 @@
99101
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
100102
isa = PBXGroup;
101103
children = (
104+
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */,
102105
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
103106
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */,
104107
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */,
105108
5442E23325FC528400A26F43 /* DetailViewController.swift */,
106109
5442E24325FC538200A26F43 /* InspectorViewController.swift */,
107-
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
108110
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */,
109111
5442E23925FC52CD00A26F43 /* ModalViewController.swift */,
110112
5442E22725FC51E200A26F43 /* MultiPanelController.swift */,
111113
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
112114
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
113-
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */,
114115
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */,
115116
);
116117
path = ContentViewControllers;
@@ -163,6 +164,16 @@
163164
path = UseCases;
164165
sourceTree = "<group>";
165166
};
167+
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */ = {
168+
isa = PBXGroup;
169+
children = (
170+
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
171+
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */,
172+
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */,
173+
);
174+
path = AdaptiveLayout;
175+
sourceTree = "<group>";
176+
};
166177
5D82A6AB28D18438006A44BA /* Frameworks */ = {
167178
isa = PBXGroup;
168179
children = (
@@ -244,10 +255,11 @@
244255
buildActionMask = 2147483647;
245256
files = (
246257
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
258+
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */,
247259
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
248260
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
249261
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
250-
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */,
262+
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */,
251263
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
252264
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
253265
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */,

Examples/Samples/Sources/Base.lproj/Main.storyboard

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
33
<device id="retina6_1" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
88
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
99
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -801,10 +801,10 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
801801
</objects>
802802
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
803803
</scene>
804-
<!--Adaptive Layout Test View Controller-->
804+
<!--Table View Controller For Adaptive Layout-->
805805
<scene sceneID="rDI-lU-wEx">
806806
<objects>
807-
<viewController storyboardIdentifier="AdaptiveLayoutTestViewController" id="5nC-6E-bXf" customClass="AdaptiveLayoutTestViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
807+
<viewController storyboardIdentifier="TableViewControllerForAdaptiveLayout" id="5nC-6E-bXf" customClass="TableViewControllerForAdaptiveLayout" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
808808
<view key="view" contentMode="scaleToFill" id="jXL-Ss-NCJ">
809809
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
810810
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -861,13 +861,13 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
861861
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
862862
</systemColor>
863863
<systemColor name="systemOrangeColor">
864-
<color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
864+
<color red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
865865
</systemColor>
866866
<systemColor name="systemPurpleColor">
867-
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
867+
<color red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
868868
</systemColor>
869869
<systemColor name="systemTealColor">
870-
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
870+
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
871871
</systemColor>
872872
</resources>
873873
</document>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
2+
3+
import UIKit
4+
import FloatingPanel
5+
6+
@available(iOS 13.0, *)
7+
class CollectionViewControllerForAdaptiveLayout: UIViewController {
8+
class PanelLayout: FloatingPanelLayout {
9+
let position: FloatingPanelPosition = .bottom
10+
let initialState: FloatingPanelState = .full
11+
12+
private unowned var targetGuide: UILayoutGuide
13+
14+
init(targetGuide: UILayoutGuide) {
15+
self.targetGuide = targetGuide
16+
}
17+
18+
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
19+
return [
20+
.full: FloatingPanelAdaptiveLayoutAnchor(
21+
absoluteOffset: 0.0,
22+
contentLayout: targetGuide,
23+
referenceGuide: .superview,
24+
contentBoundingGuide: .safeArea
25+
),
26+
.half: FloatingPanelAdaptiveLayoutAnchor(
27+
fractionalOffset: 0.5,
28+
contentLayout: targetGuide,
29+
referenceGuide: .superview,
30+
contentBoundingGuide: .safeArea
31+
),
32+
]
33+
}
34+
}
35+
36+
enum LayoutType {
37+
case flow
38+
case compositional
39+
}
40+
41+
weak var collectionView: UICollectionView!
42+
var layoutType: LayoutType = .flow
43+
44+
override func viewDidLoad() {
45+
super.viewDidLoad()
46+
setupCollectionView()
47+
}
48+
49+
private func setupCollectionView() {
50+
let collectionViewLayout = {
51+
switch layoutType {
52+
case .flow:
53+
CollectionViewLayoutFactory.flowLayout
54+
case .compositional:
55+
CollectionViewLayoutFactory.compositionalLayout
56+
}
57+
}()
58+
let collectionView = IntrinsicCollectionView(
59+
frame: .zero,
60+
collectionViewLayout: collectionViewLayout
61+
)
62+
63+
collectionView.delegate = self
64+
collectionView.dataSource = self
65+
collectionView.backgroundColor = .yellow
66+
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
67+
68+
view.addSubview(collectionView)
69+
self.collectionView = collectionView
70+
71+
collectionView.translatesAutoresizingMaskIntoConstraints = false
72+
NSLayoutConstraint.activate([
73+
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
74+
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
75+
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
76+
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
77+
])
78+
}
79+
}
80+
81+
@available(iOS 13.0, *)
82+
extension CollectionViewControllerForAdaptiveLayout: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
83+
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
84+
return 5 // Only three cells needed to fill the space
85+
}
86+
87+
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
88+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as! Cell
89+
cell.configure(text: "Item \(indexPath.row)")
90+
return cell
91+
}
92+
93+
94+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
95+
let width = collectionView.frame.width
96+
return CGSize(width: width, height: 100)
97+
}
98+
}
99+
100+
@available(iOS 13.0, *)
101+
extension CollectionViewControllerForAdaptiveLayout {
102+
enum CollectionViewLayoutFactory {
103+
static var flowLayout: UICollectionViewLayout {
104+
let layout = UICollectionViewFlowLayout()
105+
layout.scrollDirection = .vertical
106+
layout.minimumLineSpacing = 8 // Vertical spacing between rows
107+
return layout
108+
}
109+
110+
@available(iOS 13.0, *)
111+
static var compositionalLayout: UICollectionViewLayout {
112+
UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
113+
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
114+
let item = NSCollectionLayoutItem(layoutSize: itemSize)
115+
116+
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
117+
118+
let section = NSCollectionLayoutSection(group: group)
119+
section.interGroupSpacing = 8 // Spacing between each group/item
120+
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
121+
122+
return section
123+
}
124+
}
125+
}
126+
127+
128+
private final class Cell: UICollectionViewCell {
129+
static let reuseIdentifier = "Cell"
130+
131+
private let label: UILabel = {
132+
let label = UILabel()
133+
label.textAlignment = .center
134+
label.textColor = .white
135+
return label
136+
}()
137+
138+
override init(frame: CGRect) {
139+
super.init(frame: frame)
140+
commonInit()
141+
}
142+
143+
required init?(coder: NSCoder) {
144+
super.init(coder: coder)
145+
commonInit()
146+
}
147+
148+
private func commonInit() {
149+
backgroundColor = .systemBlue
150+
addSubview(label)
151+
label.translatesAutoresizingMaskIntoConstraints = false
152+
NSLayoutConstraint.activate([
153+
label.centerXAnchor.constraint(equalTo: centerXAnchor),
154+
label.centerYAnchor.constraint(equalTo: centerYAnchor)
155+
])
156+
}
157+
158+
func configure(text: String) {
159+
label.text = text
160+
}
161+
}
162+
163+
private final class IntrinsicCollectionView: UICollectionView {
164+
override public var contentSize: CGSize {
165+
didSet {
166+
invalidateIntrinsicContentSize()
167+
}
168+
}
169+
170+
override public var intrinsicContentSize: CGSize {
171+
layoutIfNeeded()
172+
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
173+
}
174+
}
175+
}

Examples/Samples/Sources/ContentViewControllers/AdaptiveLayoutTestViewController.swift renamed to Examples/Samples/Sources/ContentViewControllers/AdaptiveLayout/TableViewControllerForAdaptiveLayout.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import UIKit
44
import FloatingPanel
55

6-
final class AdaptiveLayoutTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
6+
final class TableViewControllerForAdaptiveLayout: UIViewController, UITableViewDataSource, UITableViewDelegate {
77
class PanelLayout: FloatingPanelLayout {
88
let position: FloatingPanelPosition = .bottom
99
let initialState: FloatingPanelState = .full
@@ -75,7 +75,6 @@ final class AdaptiveLayoutTestViewController: UIViewController, UITableViewDataS
7575
}
7676

7777
class IntrinsicTableView: UITableView {
78-
7978
override var contentSize:CGSize {
8079
didSet {
8180
invalidateIntrinsicContentSize()

0 commit comments

Comments
 (0)