4 |
5 | ## THE ENTIRE PROJECT WAS MOVED TO THE NEW HOME AND IT'S NOW CALLED OWL.
6 | ## [https://github.com/malcommac/Owl](https://github.com/malcommac/Owl)
7 | ### This repository will be removed in few months.
8 |
9 | --
10 |
11 |
12 | [](http://cocoadocs.org/docsets/FlowKitManager) [](http://cocoadocs.org/docsets/FlowKitManager) [](http://cocoadocs.org/docsets/FlowKitManager)
13 | [](https://img.shields.io/cocoapods/v/FlowKitManager.svg)
14 | [](https://github.com/Carthage/Carthage)
15 | [](http://twitter.com/danielemargutti)
16 |
17 |
★★ Star me to follow the project! ★★
18 | Created by Daniele Margutti - danielemargutti.com
19 |
20 |
21 | ## What's FlowKit
22 | FlowKit is a new approach to create, populate and manage `UITableView` and `UICollectionView`.
23 |
24 | With a declarative and type-safe approach you **don't need to implement datasource/delegate** anymore: your code is easy to read, maintain and [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)).
25 |
26 | Want to know more about FlowKit?
27 |
28 | **I've made an introductory article: [click here to read it now!](http://danielemargutti.com/2018/04/23/tables-collections-with-declarative-approach/)**
29 |
30 | ## Features Highlights
31 |
32 | - **No more datasource/delegate**: you don't need to implements tons of methods just to render your data. Just plain understandable methods to manage what kind of data you want to display, remove or move.
33 | - **Type-safe**: register pair of model/cell types you want to render, then access to instances of them in pure Swift type-safe style.
34 | - **Auto-Layout**: self-sized cells are easy to be configured, both for tables and collection views.
35 | - **Built-In Auto Animations**: changes & sync between your datasource and table/collection is evaluated automatically and you can get animations for free.
36 | - **Compact Code**: your code for table and collection is easy to read & maintain; changes in datasource are done declaratively via `add`/`move` and `remove` functions (both for sections, header/footers and single rows).
37 |
38 |
39 | ## What you will get
40 | The following code is just a small example which shows how to make a simple Contacts list UITableView using FlowKit (it works similar with collection too):
41 |
42 | ```swift
43 | // Create a director which manage the table/collection
44 | let director = TableDirector(self.tableView)
45 |
46 | // Declare an adapter which renders Contact Model using ContactCell
47 | let cAdapter = TableAdapter()
48 |
49 | // Hook events you need
50 | // ...dequeue
51 | cAdapter.on.dequeue = { ctx in
52 | self.fullNameLabel.text = "\(ctx.model.firstName) \(ctx.model.lastName)"
53 | self.imageView.image = loadFromURL(ctx.model.avatarURL)
54 | }
55 | // ...tap (or all the other events you typically have for tables/collections)
56 | cAdapter.on.tap = { ctx in
57 | openDetail(forContact: ctx.model)
58 | }
59 |
60 | // Register adapter; now the director know how to render your data
61 | director.register(adapter: cAdapter)
62 |
63 | // Manage your source by adding/removing/moving sections & rows
64 | director.add(models: arrayOfContacts)
65 | // Finally reload your table
66 | director.reloadData()
67 | ```
68 |
69 | Pretty simple uh?
70 |
71 | No datasource, no delegate, just a declarative syntax to create & manage your data easily and in type-safe manner (both for models and cells).
72 | Learn more about sections, header/footer & events by reading the rest of guide.
73 |
74 |
75 | ## Table Of Contents
76 |
77 | - [Installation](#installation)
78 | - [Documentation](#documentation)
79 | - [Requirements](#requirements)
80 |
81 |
82 |
83 | ## Documentation
84 |
85 | The following guide explain how to use features available in FlowKit with a real example.
86 | If you want to see a live example open `FlowKit.xcodeproj` and run the `Example` app.
87 |
88 | - [Overview](#overview)
89 | - [Create the Director](#createdirector)
90 | - [Register Adapters](#registeradapters)
91 | - [Create Data Models](#createdatamodels)
92 | - [Create Cells](#createcells)
93 | - [Add Sections](#addsections)
94 | - [Manage Models/Items in Section](#managemodels)
95 | - [Setup Headers & Footers](#setupheadersfooters)
96 | - [Reload Data with/out Animations](#reloaddata)
97 | - [Listen for Events](#events)
98 | - [Sizing Cells](#sizingcells)
99 |
100 | **Note**: *The following concepts are valid even if work with tables or collections using FlowKit (each class used starts with `Table[...]` or `Collection[...]` prefixes and where there are similaties between functions the name of functions/properties are consistence).*
101 |
102 | ### Overview
103 |
104 | The following graph describe the infrastructure of FlowKit for Collection (the same graph is [also available for Tables](Documentation/Structure_TableKit.png))).
105 |
106 | 
107 |
108 | The most important class of FlowKit is the Director; this class (`TableDirector` for tables, `CollectionDirector`/`FlowCollectionDirector` for collections) manage the sync between the data and the UI: you can add/remove/move sections and configure the appearance and the behaviour of the list directly from this instance.
109 | The first step to use FlowKit is to assign a director to your list: you can do it by calling `list.director` or just creating your own director with the list instance to manage:
110 |
111 | ```swift
112 | let director = FlowCollectionDirector(self.collectionView)
113 | ```
114 |
115 | In order to render some data FlowKit must know what kind of data you want to show into the list; data is organized as pair of `` (where `Model` is the object you want to add into the table and view is the cell used to represent the data).
116 | **A `Model` must be an object (both class or struct) conform to `ModelProtocol`: this is a simple protocol which require the presence of a property `modelID`.
117 | This property is used to uniquely identify the model and evaluate the difference between items during automatic reload with animations.**
118 |
119 | Adapter also allows to receive events used to configure the view and the behaviour: you can intercept tap for an instance of your model and do something, or just fillup received type-safe cell instance with model instance.
120 |
121 | So, as second step, you need to register some adapters:
122 |
123 | ```swift
124 | let adapter = CollectionAdapter()
125 | adapter.on.tap = { ctx in
126 | print("User tapped on \(ctx.model.firstName)")
127 | }
128 | adapter.on.dequeue = { ctx in
129 | ctx.cell?.titleLabel?.text = ctx.model.firstName
130 | }
131 | ```
132 |
133 | Now you are ready to create sections with your models inside:
134 |
135 | ```swift
136 | let section = TableSection(headerTitle: "Contacts", models: contactsList)
137 | self.tableView.director.add(section: section)
138 | ```
139 |
140 | Models array can be etherogenous, just remeber to make your objects conform to `ModelProtocol` and `Hashable` protocol and register the associated adapter. FlowKit will take care to call your adapter events as it needs.
141 |
142 | Finally you can reload the data:
143 |
144 | ```swift
145 | self.tableView.reloadData()
146 | ```
147 |
148 | Et voilà! In just few lines of code you have created e managed even complex lists.
149 |
150 | The following guide describe all the other features of the library.
151 |
152 |
153 |
154 |
155 | ### Create the Director (`TableDirector`/`CollectionDirector`)
156 |
157 | You can think about the Director has the owner/manager of the table/collection: using it you can declare what kind of data your scroller is able to show (both models and views/cells), add/remove/move both sections and items in sections.
158 | Since you will start using FlowKit you will use the director instance to manage the content of the UI.
159 |
160 | *Keep in mind: a single director instance is able to manage only a single instance of a scroller.*
161 |
162 | You have two ways to set a director; explicitly:
163 |
164 | ```swift
165 | public class ViewController: UIViewController {
166 | @IBOutlet public var tableView: UITableView?
167 | private var director: TableDirector?
168 |
169 | override func viewDidLoad() {
170 | super.viewDidLoad()
171 | self.director = TableDirector(self.tableView!)
172 | }
173 | }
174 | ```
175 |
176 | or using implicitly, by accessing to the `director` property: the first time you call it a new director instance is created and assigned with strong reference to the table/collection instance.
177 |
178 | **Note:** For UICollectionView `FlowCollectionDirector` is created automatically; if you use another layout you must create a new one manually.
179 |
180 | ```swift
181 | let director = self.tableView.director // create a director automatically and assign as strong reference to the table/collection
182 | // do something with it...
183 | ```
184 |
185 |
186 |
187 | ### Register Adapters (`TableAdapter`/`CollectionAdapter`)
188 |
189 | Once you have a director you need to tell to it what kind of data you are about to render: you can have an etherogeneus collection of Models and View (cells) in your scroller but a single Model can be rendered to a single type of View.
190 |
191 | Suppose you want to render two types of models:
192 |
193 | - `Contact` instances using `ContactCell` view
194 | - `ContactGroup` instances using `ContactGroupCell` view
195 |
196 | You will need two adapters:
197 |
198 | ```swift
199 | let contactAdpt = TableAdapter()
200 | let groupAdpt = TableAdapter()
201 | tableView.director.register(adapters: [contactAdpt, groupAdpt])
202 | ```
203 |
204 | Now you are ready to present your data.
205 |
206 |
207 |
208 | ### Create Data Models (`ModelProtocol`)
209 |
210 | In order to render your data each object of the scroller must conforms to `ModelProtocol`, a simple protocol which require the implementation of `modelID` property (an `Int`). This property is used to uniquely identify the model and evaluate the difference between items during automatic reload with animations.
211 | A default implementation of this property is available for class based object (`AnyObject`) which uses the `ObjectIdentifier()`.
212 | Instead an explicit implementation must be provided for value based objects (ie. Structs).
213 |
214 | This is an example implementation of `Contact` model:
215 |
216 | ```swift
217 | public class Contact: ModelProtocol {
218 | public var name: String
219 | public var GUID: String = NSUUID().uuidString
220 |
221 | public var id: Int {
222 | return GUID.hashValue
223 | }
224 |
225 | public static func == (lhs: Contact, rhs: Contact) -> Bool {
226 | return lhs.GUID == rhs.GUID
227 | }
228 |
229 | public init(_ name: String) {
230 | self.name = name
231 | }
232 | }
233 | ```
234 |
235 |
236 |
237 | ### Create Cells (`UITableViewCell`/`UICollectionViewCell`)
238 |
239 | Both `UITableViewCell` and `UICollectionViewCell` and its subclasses are automatically conforms to `CellProtocol`.
240 |
241 | The only constraint is about `reuseIdentifier`: **cells must have`reuseIdentifier` (`Identifier` in Interface Builder) the name of the class itself.**.
242 |
243 | If you need you can override this behaviour by overrinding the `reuseIdentifier: String` property of your cell and returing your own identifier.
244 |
245 | Cell can be loaded in three ways:
246 |
247 | - **Cells from Storyboard**: This is the default behaviour; you don't need to do anything, cells are registered automatically.
248 | - **Cells from XIB files**: Be sure your xib files have the same name of the class (ie. `ContactCell.xib`) and the cell as root item.
249 | - **Cells from `initWithFrame`**: Override `CellProtocol`'s `registerAsClass` to return `true`.
250 |
251 | This is a small example of the `ContactCell`:
252 |
253 | ```swift
254 | public class ContactCell: UITableViewCell {
255 | @IBOutlet public var labelName: UILabel?
256 | @IBOutlet public var labelSurname: UILabel?
257 | @IBOutlet public var icon: UIImageView?
258 | }
259 | ```
260 |
261 |
262 |
263 | ### Add Sections (`TableSection`/`CollectionSection`)
264 |
265 | Each Table/Collection must have at least one section to show something.
266 | `TableSection`/`CollectionSection` instances hold the items to show (into `.models` property) and optionally any header/Footer you can apply.
267 |
268 | In order to manage sections of your table you need to use the following methods of the parent Director:
269 |
270 | - `set(models:)` change the section of the table/collection.
271 | - `section(at:)` return section at index.
272 | - `firstSection()` return first section, if any.
273 | - `lastSection()` return last section, if any.
274 | - `add(section:at:)` add or insert a new section.
275 | - `add(sections:at:)` add an array of sections.
276 | - `add(models:)` create a new section with given models inside and add it.
277 | - `removeAll(keepingCapacity:)` remove all sections.
278 | - `remove(section:)` remove section at given index.
279 | - `remove(sectionsAt:)` remove sections at given indexes set.
280 | - `move(swappingAt:with:)` swap section at given index with destination index.
281 | - `move(from:to)` combo remove/insert of section at given index to destination index.
282 |
283 | The following example create a new `TableSection` with some items inside, a `String` based header, then append it a the end of the table.
284 |
285 | ```swift
286 | let section = TableSection(headerTitle: "The Strangers", items: [mrBrown,mrGreen,mrWhite])
287 | table.director.add(section: section)
288 | ```
289 |
290 |
291 |
292 | ### Manage Models/Items in Section
293 |
294 | As for section the same `add`/`remove`/`move` function are also available for `models` array which describe the content (rows/items) inside each section (`TableSection`/`CollectionSection` instances).
295 |
296 | This is the complete list:
297 |
298 | - `set(models:)` change models array.
299 | - `add(model:at:)` add model at given index, if nil is append at the bottom.
300 | - `add(models:at:)` add models starting at given index; if nil models are append at the bottom.
301 | - `remove(at:)` remove model at given index.
302 | - `remove(atIndexes:)` remove models at given index set.
303 | - `removeAll(keepingCapacity:)` remove all models of the section.
304 | - `move(swappingAt:with:)` Swap model at given index to another destination index.
305 | - `move(from:to:)` Remove model at given index and insert at destination index.
306 |
307 | After any change you must call the `director.reloadData()` function from main thread to update the UI.
308 |
309 | This is an example of items management:
310 |
311 | ```swift
312 | let section = self.tableView.director.firstSection()
313 |
314 | let newItems = [Contact("Daniele","Margutti"),Contact("Fabio","Rossi")]
315 | section.add(models: newItems) // add two new contacts
316 | section.remove(at: 0) // remove first item
317 |
318 | // ...
319 | self.tableView.director.reloadData() // reload data
320 | ```
321 |
322 |
323 |
324 | ### Setup Headers & Footers (`TableSectionView`/`CollectionSectionView`)
325 |
326 | **Simple Header/Footer**
327 |
328 | Section may have or not headers/footers; these can be simple `String` (as you seen above) or custom views.
329 |
330 | Setting simple headers is pretty straightforward, just set the `headerTitle`/`footerTitle`:
331 |
332 | ```swift
333 | section.headerTitle = "New Strangers"
334 | section.footerTitle = "\(contacts.count) contacts")
335 | ```
336 | **Custom View Header/Footer**
337 |
338 | To use custom view as header/footer you need to create a custom `xib` file with a `UITableViewHeaderFooterView` (for table) or `UICollectionReusableView` (for collection) view subclass as root item.
339 |
340 | The following example shows a custom header and how to set it:
341 |
342 | ```swift
343 | // we also need of TableExampleHeaderView.xib file
344 | // with TableExampleHeaderView view as root item
345 | public class TableExampleHeaderView: UITableViewHeaderFooterView {
346 | @IBOutlet public var titleLabel: UILabel?
347 | }
348 |
349 | // create the header container (will receive events)
350 | let header = TableSectionView()
351 | // hooks any event. You need at least the `height` as below
352 | header.on.height = { _ in
353 | return 150
354 | }
355 |
356 | // Use it
357 | let section = TableSection(headerView: header, items: [mrBrown,mrGreen,mrWhite])
358 | table.director.add(section: section)
359 | ```
360 |
361 |
362 |
363 | ### Reload Data with/out Animations
364 |
365 | Each change to the data model must be done by calling the `add`/`remove`/`move` function available both at sections and items levels.
366 | After changes you need to call the director's `reloadData()` function to update the UI.
367 |
368 | The following example update a table after some changes:
369 |
370 | ```swift
371 | // do some changes
372 | tableView.director.remove(section: 0)
373 | tableView.director.add(section: newSection, at: 2)
374 | ...
375 | tableView.director.firstSection().remove(at: 0)
376 |
377 | // then reload
378 | tableView.director.reloadData()
379 | ```
380 |
381 | If you need to perform an animated reload just make your changes to the model inside the callback available into the method.
382 | Animations are evaluated and applied for you!
383 |
384 | ```swift
385 | tableView.director.reloadData(after: { _ in
386 | tableView.director.remove(section: 0)
387 | tableView.director.add(section: newSection, at: 2)
388 |
389 | return TableReloadAnimations.default()
390 | })
391 | ```
392 |
393 | For `TableDirector` you must provide a `TableReloadAnimations` configuration which defines what kind of `UITableViewRowAnimation` must be applied for each type of change (insert/delete/reload/move). `TableReloadAnimations.default()` just uses `.automatic` for each type (you can also implement your own object which need to be conform to `TableReloadAnimationProtocol` protocol).
394 |
395 | For `CollectionDirector` you don't need to return anything; evaluation is made for you based upon the layout used.
396 |
397 |
398 |
399 |
400 | ## Listen for Events
401 |
402 | All events are hookable from their respective objects starting from `.on` property. All standard table & collections events are available from FlowKit; name of the events is similar to the their standard corrispettive in UIKit (see the official documentation for more info abou any specific event).
403 |
404 | - [Table Events](/Documentation/Table_Events.md)
405 | - [Collection Events](/Documentation/Collection_Events.md)
406 | - [UIScrollViewDelegate Events](/Documentation/UIScrollViewDelegate_Events.md)
407 |
408 |
409 |
410 | ## Sizing Cells
411 |
412 | FlowKit support easy cell sizing using autolayout.
413 | You can set the size of the cell by adapter or collection based. For autolayout driven cell sizing set the `rowHeight` (for `TableDirector`) or `itemSize` (for `CollectionDirector`/`FlowCollectionDirector`) to the `autoLayout` value, then provide an estimated value.
414 |
415 | Accepted values are:
416 | - `default`: you must provide the height (table) or size (collection) of the cell
417 | - `autoLayout`: uses autolayout to evaluate the height of the cell; for Collection Views you can also provide your own calculation by overriding `preferredLayoutAttributesFitting()` function in cell instance.
418 | - `fixed`: provide a fixed height for all cell types (faster if you plan to have all cell sized same)
419 |
420 |
421 |
422 |
423 | ## Installation
424 |
425 | ### Install via CocoaPods
426 |
427 | [CocoaPods](http://cocoapods.org) is a dependency manager which automates and simplifies the process of using 3rd-party libraries like FlowKit in your projects. You can install it with the following command:
428 |
429 | ```bash
430 | $ sudo gem install cocoapods
431 | ```
432 |
433 | > CocoaPods 1.0.1+ is required to build FlowKit.
434 |
435 | #### Install via Podfile
436 |
437 | To integrate FlowKit into your Xcode project using CocoaPods, specify it in your `Podfile`:
438 |
439 | ```ruby
440 | source 'https://github.com/CocoaPods/Specs.git'
441 | platform :ios, '8.0'
442 |
443 | target 'TargetName' do
444 | use_frameworks!
445 | pod 'FlowKitManager'
446 | end
447 | ```
448 |
449 | Then, run the following command:
450 |
451 | ```bash
452 | $ pod install
453 | ```
454 |
455 |
456 |
457 | ### Carthage
458 |
459 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
460 |
461 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
462 |
463 | ```bash
464 | $ brew update
465 | $ brew install carthage
466 | ```
467 |
468 | To integrate FlowKit into your Xcode project using Carthage, specify it in your `Cartfile`:
469 |
470 | ```ogdl
471 | github "malcommac/FlowKitManager"
472 | ```
473 |
474 | Run `carthage` to build the framework and drag the built `FlowKit.framework` into your Xcode project.
475 |
476 |
477 |
478 | ## Requirements
479 |
480 | FlowKit is compatible with Swift 4.x.
481 |
482 | * iOS 8.0+
483 |
--------------------------------------------------------------------------------