Skip to content

Commit a57ec59

Browse files
committed
Completed readme
1 parent 4b808ad commit a57ec59

File tree

4 files changed

+266
-12
lines changed

4 files changed

+266
-12
lines changed

README.md

Lines changed: 252 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# ScrollStackController
22
Create complex scrollable layout using UIViewController and simplify your code!
33

4+
[![Version](https://img.shields.io/cocoapods/v/OwlKit.svg?style=flat)](http://cocoadocs.org/docsets/ScrollStackController)
5+
[![License](https://img.shields.io/cocoapods/l/OwlKit.svg?style=flat)](http://cocoadocs.org/docsets/ScrollStackController)
6+
[![Platform](https://img.shields.io/cocoapods/p/OwlKit.svg?style=flat)](http://cocoadocs.org/docsets/ScrollStackController)
7+
[![CI Status](https://travis-ci.org/malcommac/OwlKit.svg)](https://travis-ci.org/malcommac/ScrollStackController)
8+
![Twitter Follow](https://img.shields.io/twitter/follow/danielemargutti.svg?label=Follow&style=flat-square)
9+
10+
ScrollStackController was created and maintaned by [Daniele Margutti](https://github.com/malcommac) - My home site [www.danielemargutti.com](https://www.danielemargutti.com).
11+
412
## Introduction
513
ScrollStackController is a class you can use to create complex layouts using scrollable `UIStackView` but where each row is handled by a separate `UIViewController`; this allows you to keep a great separation of concerns.
614

@@ -12,16 +20,26 @@ You can think of it as `UITableView` but with several differences:
1220

1321
## Table of Contents
1422

15-
- Main Features
16-
- System Requirements
17-
- How to use it
18-
- Adding Rows
19-
- Removing / Replacing Rows
20-
- Move Rows
21-
- Hide / Show Rows
22-
- Reload Rows
23-
- Sizing Rows
24-
23+
- [Main Features](#mainfeatures)
24+
- [System Requirements](#systemrequirements)
25+
- [When to use `ScrollStackController` and when not](#whentousescrollstackcontrollerandwhennot)
26+
- [How to use it](#howtouseit)
27+
- [Adding Rows](#addingrows)
28+
- [Removing / Replacing Rows](#removingreplacingrows)
29+
- [Move Rows](#moverows)
30+
- [Hide / Show Rows](#hideshowrows)
31+
- [Reload Rows](#reloadrows)
32+
- [Sizing Rows](#sizingrows)
33+
- [Fixed Row Size](#fixedrowsize)
34+
- [Fitting Layout Row Size](#fittinglayoutrowsize)
35+
- [Collapsible Rows](#collapsiblerows)
36+
- [Working with dynamic UICollectionView/UITableView/UITextView](#workingwithdynamicuicollectionviewuitableviewuitextview)
37+
- [Rows Separator](#rowsseparator)
38+
- [Tap On Rows](#taponrows)
39+
- [Installation](#installation)
40+
- A[uthor & License](#authorlicense)
41+
42+
<a name="mainfeatures"/>
2543
### Main Features
2644

2745

@@ -35,12 +53,33 @@ You can think of it as `UITableView` but with several differences:
3553
| 🧬 | It uses standard UIKit components at its core. No magic, just a combination of `UIScrollView`+`UIStackView`. |
3654
| 🐦 | Fully made in Swift 5 from Swift ❥ lovers |
3755

56+
<a name="systemrequirements"/>
3857
### System Requirements
3958

4059
- iOS 9+
4160
- Xcode 10+
4261
- Swift 5+
4362

63+
<a name="whentousescrollstackcontrollerandwhennot"/>
64+
### When to use `ScrollStackController` and when not
65+
66+
##### Yes
67+
68+
`ScrollStackController` is best used for shorter screens with an heterogeneous set of rows: in these cases you don't need to have view recycling.
69+
70+
Thanks to autolayout you will get updates and animations for free.
71+
72+
You can also manage each screen independently with a great separation of concerns; morehover unlike `UITableView` and `UICollectionView`, you can keep strong references to `UIViewController` (and its views) in an `ScrollStack` view and make changes to them at any point.
73+
74+
#### No
75+
76+
`ScrollStackController` is not suitable in all situations.
77+
`ScrollStackController` lays out the entire UI at first time when your screen loads.
78+
If you have a long list of rows you may experience delays.
79+
80+
So, `ScrollStackController` is generally not appropriate for screens that contain many views of the same type, all showing similar data (in these cases you should use `UITableView` or `UICollectionView`).
81+
82+
<a name="howtouseit"/>
4483
### How to use it
4584

4685
The main class of the package is `ScrollStack`, a subclass of `UIScrollView`. It manages the layout of each row, animations and keep a strong reference to your rows.
@@ -89,6 +128,7 @@ let lastRow = scrollStack.lastRow
89128

90129
Let's take a look below.
91130

131+
<a name="addingrows"/>
92132
#### Adding Rows
93133

94134
`ScrollStack` provides a comprehensive set of methods for managing rows, including inserting rows at the beginning and end, inserting rows above or below other rows.
@@ -117,6 +157,7 @@ The following code add a rows with the view of each view controller passed:
117157

118158
As you noticed there is not need to keep a strong reference to any view controller; they are automatically strong referenced by each row created to add them into the stack.
119159

160+
<a name="removingreplacingrows"/>
120161
#### Removing / Replacing Rows
121162

122163
A similar set of APIs are used to remove existing rows from the stack:
@@ -135,6 +176,7 @@ stackView.replaceRow(index: 1, withRow: galleryVC, animated: true) {
135176
}
136177
```
137178

179+
<a name="moverows"/>
138180
#### Move Rows
139181

140182
If you need to adjust the hierarchy of the stack by moving a row from a position to another you can use:
@@ -148,6 +190,7 @@ let randomDst = Int.random(in: 1..<stackView.rows.count)
148190
stackView.moveRow(index: 0, to: randomDst, animated: true, completion: nil)
149191
```
150192

193+
<a name="hideshowrows"/>
151194
#### Hide / Show Rows
152195

153196
`ScrollStack` uses the power of `UIStackView`: you can show and hide rows easily with a gorgeous animation by using one of the following methods:
@@ -163,6 +206,7 @@ stackView.setRowsHidden(indexes: [0,1,2], isHidden: true, animated: true)
163206

164207
Keep in mind: when you hide a rows the row still part of the stack and it's not removed, just hidden! If you get the list of rows by calling `rows` property of the `ScrollStack` you still see it.
165208

209+
<a name="reloadrows"/>
166210
#### Reload Rows
167211

168212
Reload rows method allows you to refresh the layout of the entire stack (using `layoutIfNeeded()`) while you have a chance to update a specific row's `contentView` (aka the view of the managed `UIViewController`).
@@ -199,6 +243,7 @@ class GalleryVC: UIViewController, ScrollStackContainableController {
199243
}
200244
```
201245

246+
<a name="sizingrows"/>
202247
#### Sizing Rows
203248

204249
You can control the size of your `UIViewController` inside a row of a `ScrollStack` in two ways:
@@ -208,3 +253,200 @@ You can control the size of your `UIViewController` inside a row of a `ScrollSta
208253

209254
In both case `ScrollStack` class will use only one dimension depending by the active scroll axis to layout the view controller content into the stack (if scroll axis is `horizontal` you can control only the `height` of the row, if it's `vertical` only the `width`. The other dimension will be the same of the scroll stack itself.
210255

256+
Each of the following cases is covered inside the demo application:
257+
258+
- Fixed row size in [GalleryVC](https://github.com/malcommac/ScrollStackController/blob/master/ScrollStackControllerDemo/Child%20View%20Controllers/GalleryVC.swift)
259+
- Collapsible / Expandable row in [TagsVC](https://github.com/malcommac/ScrollStackController/blob/master/ScrollStackControllerDemo/Child%20View%20Controllers/TagsVC.swift)
260+
- Growing row based on `UITextView`'s content in [NotesVC](https://github.com/malcommac/ScrollStackController/blob/master/ScrollStackControllerDemo/Child%20View%20Controllers/NotesVC.swift)
261+
- Growing row based on `UITableView`'s content in [PricingVC](https://github.com/malcommac/ScrollStackController/blob/master/ScrollStackControllerDemo/Child%20View%20Controllers/PricingVC.swift)
262+
263+
<a name="fixedrowsize"/>
264+
#### Fixed Row Size
265+
266+
If your view controller has a fixed size you can just return it as follows:
267+
268+
```swift
269+
270+
class GalleryVC: UIViewController, ScrollStackContainableController {
271+
272+
public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
273+
switch axis {
274+
case .horizontal:
275+
return .fixed(300)
276+
case .vertical:
277+
return .fixed(500)
278+
}
279+
}
280+
281+
}
282+
283+
```
284+
285+
If your stack support single axis you can obivously avoid switch condition.
286+
When you will add this view controller in a scroll stack it will be sized as you requested (any height/width constraint already in place will be removed).
287+
288+
<a name="fittinglayoutrowsize"/>
289+
#### Fitting Layout Row Size
290+
291+
Sometimes you may want to have the content view sized by fitting the contents of the view controller's view. In these cases you can use `. fitLayoutForAxis`.
292+
293+
Example:
294+
295+
```swift
296+
public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
297+
return .fitLayoutForAxis
298+
}
299+
```
300+
301+
`ScrollStack` will use the `systemLayoutSizeFitting()` method on your view controller's view to get the best size to fit the content.
302+
303+
<a name="collapsiblerows"/>
304+
#### Collapsible Rows
305+
306+
Sometimes you may want to create collapsible rows.
307+
These row can have different heights depending of a variable.
308+
309+
In this case you just need to implement a `isExpanded: Bool` variable in your view controller and return a different height based on it.
310+
311+
```swift
312+
313+
public class TagsVC: UIViewController, ScrollStackContainableController {
314+
315+
public var isExpanded = false
316+
317+
public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
318+
return (isExpanded == false ? .fixed(170) : .fixed(170 + collectionView.contentSize.height + 20))
319+
}
320+
}
321+
```
322+
323+
In your main view controller you may call this:
324+
325+
```swift
326+
// get the first row which manages this controller
327+
let tagsRow = stackView.firstRowForControllerOfType(TagsVC.self)
328+
// or if you have already the instance you can get the row directly
329+
// let tagsRow = stackView.rowForController(tagsVCInstance)
330+
331+
let tagsVCInstance = (tagsRow.controller as! TagsVC)
332+
tagsVCInstance.isExpanded = !tagsVCInstance.isExpanded
333+
334+
stackView.reloadRow(tagsRow, animated: true)
335+
```
336+
337+
And your rows will perform a great animation to resize its content.
338+
339+
<a name="workingwithdynamicuicollectionviewuitableviewuitextview"/>
340+
#### Working with dynamic UICollectionView/UITableView/UITextView
341+
342+
There are some special cases where you may need to resize the row according to the changing content in your view controller's view.
343+
344+
Consider for example an `UIViewController` with a `UITableView` inside; you may want to show the entire table content's as it grown.
345+
In this case you need to make some further changes:
346+
347+
- You need to return `.fitLayoutForAxis`.
348+
- In your view controller's view you need to create a reference to the height constraint of your table.
349+
- You need to create a constraint from the table to the bottom safe area of your view (this will be used by AL to grow the size of the view).
350+
351+
Then you must override the `updateViewConstraints()` to change the value of the table's height constraint to the right value.
352+
353+
This is the code:
354+
355+
```swift
356+
357+
public class PricingVC: UIViewController, ScrollStackContainableController {
358+
359+
public weak var delegate: PricingVCProtocol?
360+
361+
@IBOutlet public var pricingTable: UITableView!
362+
@IBOutlet public var pricingTableHeightConstraint: NSLayoutConstraint!
363+
364+
public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
365+
return .fitLayoutForAxis
366+
}
367+
368+
override public func updateViewConstraints() {
369+
pricingTableHeightConstraint.constant = pricingTable.contentSize.height // the size of the table as the size of its content
370+
view.height(constant: nil) // cancel any height constraint already in place in the view
371+
super.updateViewConstraints()
372+
}
373+
}
374+
```
375+
376+
In this way as you add new value to the table the size of the row in stack view will grown.
377+
378+
<a name="rowsseparator"/>
379+
#### Rows Separator
380+
381+
Each row managed by `ScrollStack` is of a subview class of type `ScrollStackRow`. It has a strong referenced to managed `UIViewController` but also have a subview on bottom called `ScrollStackSeparator`.
382+
383+
You can hide/show separators by using the following properties of the row:
384+
385+
- `isSeparatorHidden`: to hide separator.
386+
- `separatorInsets`: to set the insets of the sepatator (by default is set to the same value used by `UITableView` instances)
387+
- `separatorView.color`: to change the color
388+
- `separatorView.thickness`: to se the thickness of the separator (1 by default).
389+
390+
Moreover you can set these values directly on `ScrollStack` controller in order to have a default value for each new row.
391+
392+
`ScrollStack` also have a property called `autoHideLastRowSeparator` to hide the last separator of the stack automatically.
393+
394+
<a name="taponrows"/>
395+
#### Tap On Rows
396+
397+
By default rows are not tappable but if you need to implement some sort of tap features like in `UITableView` you can add it by setting a default callback for `onTap` property on `ScrollStackRow` instances.
398+
399+
For example:
400+
401+
```swift
402+
scrollStack.firstRow?.onTap = { row in
403+
// do something on tap
404+
}
405+
```
406+
407+
Once you can set a tap handler you can also provide highlight color for tap.
408+
To do it you must implement `ScrollStackRowHighlightable` protocol in your row managed view controller.
409+
410+
For example:
411+
412+
```swift
413+
class GalleryVC: UIViewController, ScrollStackRowHighlightable {
414+
415+
public var isHighlightable: Bool {
416+
return true
417+
}
418+
419+
func setIsHighlighted(_ isHighlighted: Bool) {
420+
self.view.backgroundColor = (isHighlighted ? .red : .white)
421+
}
422+
423+
}
424+
425+
```
426+
427+
Transition between highlights state will be animated automatically.
428+
429+
<a name="installation"/>
430+
### Installation
431+
432+
`ScrollStackContainer` can be installed with CocoaPods by adding pod 'ScrollStackContainer' to your Podfile.
433+
434+
```ruby
435+
pod 'ScrollStackContainer'
436+
```
437+
438+
It also supports `Swift Package Maneger` aka SPM.
439+
440+
441+
<a name="authorlicense"/>
442+
### Author & License
443+
444+
`ScrollStackContainer` is developed and maintained by:
445+
446+
- Daniele Margutti ([danielemargutti.com](http://www.danielemargutti.com) - [@danielemargutti](http://www.twitter.com/danielemargutti) on twitter)
447+
448+
I fully welcome contributions, new features, feature requests, bug reports, and fixes. Also PR are accepted!
449+
450+
`ScrollStackContainer` is released under the MIT License.
451+
It was originally inspired by [`AloeStackView`](https://github.com/airbnb/AloeStackView) by Airbnb.
452+

ScrollStackControllerDemo/Child View Controllers/PricingVC.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public class PricingVC: UIViewController, ScrollStackContainableController {
4343
}
4444

4545
override public func updateViewConstraints() {
46-
pricingTableHeightConstraint.constant = pricingTable.contentSize.height
47-
view.height(constant: nil)
46+
pricingTableHeightConstraint.constant = pricingTable.contentSize.height // the size of the table as the size of its content
47+
view.height(constant: nil) // cancel any height constraint already in place in the view
4848
super.updateViewConstraints()
4949
}
5050

Sources/ScrollStackController/ScrollStack.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,18 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
456456

457457
// MARK: - Row Appearance
458458

459+
/// Return the first row which manages a controller of given type.
460+
///
461+
/// - Parameter type: type of controller to get
462+
open func firstRowForControllerOfType<T: UIViewController>(_ type: T.Type) -> ScrollStackRow? {
463+
return rows.first {
464+
if let _ = $0.controller as? T {
465+
return true
466+
}
467+
return false
468+
}
469+
}
470+
459471
/// Return the row associated with passed `UIViewController` instance and its index into the `rows` array.
460472
///
461473
/// - Parameter controller: target controller.

0 commit comments

Comments
 (0)