Skip to content

Commit b3d6d25

Browse files
authored
Merge pull request scribd#7 from scribd/backdated-content
Import some backdated content
2 parents 4ae6058 + 9000725 commit b3d6d25

19 files changed

+1893
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ _site
33
.jekyll-cache
44
.jekyll-metadata
55
vendor
6+
*.sw*

Gemfile

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
source "https://rubygems.org"
2-
# Hello! This is where you manage which Jekyll version is used to run.
3-
# When you want to use a different version, change it below, save the
4-
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
5-
#
6-
# bundle exec jekyll serve
7-
#
8-
# This will help ensure the proper Jekyll version is running.
9-
# Happy Jekylling!
2+
103
gem "jekyll", "~> 3.8.5"
4+
gem 'kramdown'
5+
gem 'rouge'
116

127
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
138
# uncomment the line below. To upgrade, run `bundle update github-pages`.

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ DEPENDENCIES
249249
github-pages (~> 201)
250250
jekyll (~> 3.8.5)
251251
jekyll-feed (~> 0.11)
252+
kramdown
253+
rouge
252254
tzinfo (~> 1.2)
253255
tzinfo-data
254256
wdm (~> 0.1.1)

_config.yml

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,22 @@
1-
# Welcome to Jekyll!
2-
#
3-
# This config file is meant for settings that affect your whole blog, values
4-
# which you are expected to set up once and rarely edit after that. If you find
5-
# yourself editing this file very often, consider using Jekyll's data files
6-
# feature for the data you need to update frequently.
7-
#
8-
# For technical reasons, this file is *NOT* reloaded automatically when you use
9-
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10-
#
11-
# If you need help with YAML syntax, here are some quick references for you:
12-
# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13-
# https://learnxinyminutes.com/docs/yaml/
14-
#
15-
# Site settings
16-
# These are used to personalize your new site. If you look in the HTML files,
17-
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18-
# You can create any custom variable you would like, and they will be accessible
19-
# in the templates via {{ site.myvariable }}.
20-
21-
title: Scridb Technology
22-
1+
title: Scribd Technology
2+
233
description: >- # this means to ignore newlines until "baseurl:"
244
Scribd technology builds and delivers one of the world's largest libraries,
255
bringing the best books, audiobooks, and journalism to millions of people
266
around the world.
277
baseurl: "" # the subpath of your site, e.g. /blog
28-
url: "" # the base hostname & protocol for your site, e.g. http://example.com
8+
url: "https://tech.scribd.com" # the base hostname & protocol for your site, e.g. http://example.com
299

3010
# GitHub Metadata
3111
# Used for "improve this page" link
3212
branch: master
3313

14+
markdown: kramdown
15+
highlighter: rouge
16+
kramdown:
17+
input: GFM
18+
syntax_highlighter: rouge
19+
3420
twitter_username: scribd
3521
facebook_username: scribd
3622
linkedin_username: scribd

_data/authors.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#
2+
# This data file contains all the authors for the blog, with their links,
3+
# description, etc
4+
---
5+
6+
gwtrev:
7+
name: George Treviranus
8+
twitter: gwtrev
9+
about: |
10+
George is a front-end developer and digital designer living in Oakland,
11+
California. By day, he’s a front-end software engineer at Scribd. Other
12+
times, he can be found long boarding, playing video games, or napping.
13+
Usually napping.
14+
15+
bclearly:
16+
name: Brayden Clearly
17+
18+
siweiz:
19+
name: Siwei Zhu
20+
21+
soorae:
22+
name: Soo-Rae Hong
23+
24+
theo:
25+
name: Théophane Rupin
26+
about: |
27+
iOS Developer @Scribd. Creator of Weaver
28+
29+
stephane:
30+
name: Stephane Magne
31+
about: iOS Developer @ Scribd

_layouts/post.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ <h1 itemprop="name headline">{{ page.title | escape }}</h1>
1313
</time>
1414

1515
{%- if page.author -%}
16-
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>
16+
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ site.data.authors[page.author].name}}</span></span>
1717
{%- endif -%}
1818
</p>
1919
</header>
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
layout: post
3+
title: "Get Animated with LiveCollections for iOS"
4+
author: stephane
5+
tags:
6+
- swift
7+
team: ios
8+
---
9+
10+
A collection of books on Scribd
11+
12+
To get straight to the point, using UITableView and UICollectionView animations is harder than it should be. What should be a nice visual flourish in your app can end in betrayal and crashes. This is pretty much the worst case scenario, and it’s pretty hard to explain to your manager or PM that the latest release is now crashing more because you wanted to make it *prettier*. The ultimate decision is that we roll back the change and replace it with a call to reloadData, replacing animations with a jolt, and that makes me a sad developer.
13+
14+
I’ve made this concession myself more than once, when it would still fail after I thought I had caught the problem, it started to feel too fragile. I found myself becoming demoralized about using these animations. Surely a solution could be found.
15+
16+
### The Solution: Write a Framework!
17+
18+
If you’re like me, then you’d rather spend 10 hours solving the general case of a problem than solving that one specific case in an hour, only to know that someday you’re going to have to tackle that same hour again and again. In this particular case I think the time spent was actually closer to 100 hours, but who’s counting?
19+
20+
Some call getting diverted down that rabbit-hole, procrastination, I call it forward-thinking aversion. As such, **LiveCollections.framework** was born.
21+
22+
### So, What is it exactly?
23+
24+
It’s a tool for iOS developers that takes in an array of data and animates the delta from the previous array in a UITableView, UICollectionView, or even a custom view.
25+
26+
![Fg.1: Animations calculated from immutable data sets](https://cdn-images-1.medium.com/max/2048/1*uqRV_tN_WJtaB1yCijyaaQ.gif)*Fg.1: Animations calculated from immutable data sets*
27+
28+
More importantly, it doesn’t require the system that is serving up the data to give you any hints or even know about the changes in the data, a distinction from NSFetchedResultsController. Instead, LiveCollections takes two fully immutable arrays of data and animates the difference between them, either by calling the view directly, or giving you back a delta struct so that you can control the timing. This both simplifies the code you write and helps decouple how you receive the data versus how you present the data.
29+
30+
It not only manages the view animation for you, but it keeps the entire operation thread safe; so you can throw as many updates from as many different threads as carelessly as you like, and it will result in nice animations rather than an abrupt home screen.
31+
> The open source framework can be [downloaded from the Scribd GitHub account](https://github.com/scribd/LiveCollections/), and is free to use in your personal or professional applications.
32+
33+
### Risk is Removed
34+
35+
As mentioned, one of the most common problems I’ve seen using table or collection view animations is a crash that produces this dread inducing snippet:
36+
37+
***** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (27) must be equal to the number of items contained in that section before the update (30), plus or minus the number of items inserted or deleted from that section (3 inserted, 3 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'**
38+
39+
When data changes are small, an insertion here, a deletion there, then calculating the change is generally trivial. However, data is often delivered to us where the changes are unpredictable. It can be returned asynchronously from a server or background process with a wholly different immutable data set that has changed in some unknown way. In this case, calculating a delta from one data set to another is often complex and increases the chance of there being an error. Race conditions exist too, it’s possible to receive an update from another thread or process and accidentally replace the underlying data while an animation is in process. Welcome to crash town.
40+
> Having your app crash when you’re trying to add finishing polish quickly becomes a very poor risk to reward proposition. We often simply concede that it’s not worth the headache.
41+
42+
**LiveCollections takes away all of the risk of using UITableView and UICollectionView animations.** It calculates all of the deltas for you and builds up a matching animation set, and can even pass that directly to the view, queueing up subsequent updates to keep everything synchronized and thread safe.
43+
44+
This is important because…
45+
46+
### Animations Add Value
47+
48+
From a user perspective, animating changes carries with it not only a sense of polish, but actually helps us understand what is happening. When data just changes in a flash, we often miss it and as a result may end up with a sense of disorientation and be unaware of how the data has changed.
49+
50+
Apple knows this value because they’ve built in very powerful animation tools into both UITableView and UICollectionView for us to leverage. LiveCollections aims to make those tools more accessible by filling in the other half of the equation: turning changes in your data set into matching line item animation code.
51+
52+
Also importantly, Apple kept the delta logic consistent between the two views, meaning that we can support both views from the same class.
53+
54+
### How Does It Work?
55+
56+
LiveCollections provides two key classes: CollectionData and CollectionSectionData. Both of them have the same basic API:
57+
58+
collectionData.update(data)
59+
60+
Calling this one line of code with two consecutive sets of data is all it takes. It both holds the most recent set of data, and handles the transformation from the previous set when updated.
61+
62+
Additionally, the two main classes CollectionData and CollectionSectionData become your thread-safe data source from which you can access your data directly via a few simple accessors like *subscript*, *count*, and *isEmpty*.
63+
64+
To generate a delta, CollectionData takes an updated data set B’ and applies it to the existing data set B to calculate all of the positional changes for you (B∆B’). All *insertions*, *deletions*, *moves*, and *reloads*, are placed into a struct which is then passed to a UITableView or UICollectionView and all of the corresponding animations are performed automatically.
65+
66+
A delta between data sets is based on indices and looks like this:
67+
68+
IndexDelta(insertions: [0, 6, 7],
69+
deletions: [2],
70+
reloads: [(9, 10)],
71+
moves: [(8, 5), (0, 3)])
72+
73+
*(For calculating equality, it’s important to know both the position in the original data set and the position in the updated data.)*
74+
75+
### Support For Any Data Object
76+
77+
One of the goals for this project was to make a generic class to support any data type. To make your data work with CollectionData, you will extend your custom data class to adopt the protocol UniquelyIdentifiable:
78+
79+
public protocol UniquelyIdentifiable: Equatable {
80+
associatedtype RawType
81+
associatedtype UniqueIDType: Hashable
82+
83+
var rawData: RawType { get }
84+
var uniqueID: UniqueIDType { get }
85+
}
86+
87+
Here’s an example of what adopting it looks like:
88+
89+
struct Movie: Hashable {
90+
let id: UInt
91+
let title: String
92+
}
93+
94+
extension Movie: UniquelyIdentifiable {
95+
typealias RawType = Movie
96+
var uniqueID: UInt { return id }
97+
}
98+
99+
For the delta calculations to be able to identify positional changes, individual items are identified by the uniqueID property for insertions, deletions, and moves, while the equatability inheritance is used to determine if a reload is required. (In some cases an item may have both moved and require a reload).
100+
101+
In most of the examples we’ll look at RawType will always be set to the same as the adopting class. (i.e. we’ll use your class’ default equatability to determine changes). In a later example I discuss what it means to change this to another type.
102+
103+
Once you have adopted the protocol, setting up your collection data is as simple as let collectionData = CollectionData<YourClass>().
104+
> Note: As the naming implies, every single item in the data set must return a value for uniqueID that is different from every other item in the data set. This absolutely must be true or else the resulting calculations will misidentify changes and create animation deltas that crash your app!
105+
106+
Update: LiveCollections now has support for sets of non-unique data!
107+
108+
You can have your data type adopt the alternate protocol NonUniquelyIdentifiable:
109+
110+
public protocol NonUniquelyIdentifiable: Equatable {
111+
associatedtype NonUniqueIDType: Hashable
112+
var nonUniqueID: NonUniqueIDType { get }
113+
}
114+
115+
And simply replace the use of CollectionData or CollectionSectionData with typealiased alternates NonUniqueCollectionData and NonUniqueCollectionSectionData. This will be discussed in more detail in a later section.
116+
117+
### Performance
118+
119+
Delta calculations remain highly performant on data sets up to 10,000 items. Above that you will start to see some delay between setting the data and the resulting update. This is all happening on background threads so it won’t lock up your UI, but if very large updates (>10,000 items) happen frequently you may start taxing your user’s battery.
120+
121+
Here is a quick comparison of performance for two devices, an iPhone 5S and an iPhone X. A series of tests were run for two data sets, one using a simple Int and the other using a mock Book struct which has a more complex equality equation (it compares 1 integer var, 3 string vars, and a date field).
122+
123+
╔═════════════════╦═════════════╗
124+
║ **Data vs Compute** ║ **iPhone **║**
125+
**║ **Time (~seconds)** ║ **5s ** **X** ║
126+
╠═════════════════╬══════╦══════╣
127+
║ [Int] 1,000 ║ 0.05 ║ 0.01 ║
128+
║ [Int] 10,000 ║ 0.18 ║ 0.05 ║
129+
║ [Int] 100,000 ║ 1.90 ║ 0.48 ║
130+
╠–––––––––––––––––╬––––––╬––––––╣
131+
║ [Book] 1,000 ║ 0.05 ║ 0.01 ║
132+
║ [Book] 10,000 ║ 0.28 ║ 0.08 ║
133+
║ [Book] 100,000 ║ 2.99 ║ 0.90 ║
134+
╚═════════════════╩══════╩══════╝
135+
136+
Additionally, redundant calculations are not performed. If your view is animating and two rapid updates show up before the animation completes, only the delta between the current data and most recent data is calculated. Any intermediate data sets are ignored.
137+
138+
### Sample Code
139+
140+
At this point, I would encourage you to [download the framework and sample application from GitHub](https://github.com/scribd/LiveCollections/) (no dependencies required) and try out Scenario 1 to see LiveCollections in action.
141+
142+
The sample application also has a working example for every use case scenario detailed. You can jump to say, ScenarioOneViewController.swift, to see how I’ve set up the classes. Most are just a few lines of code, but if there are any specific nuances, I’ll be sure to describe them in their respective section.
143+
> All of the example scenarios in the sample app use the open API provided by The Movie Database ([www.themoviedb.org](http://www.themoviedb.org)). *I’d like to thank everyone there for providing this extremely helpful service.*
144+
145+
### Up Next: Use Cases
146+
147+
There are a number of varying use cases that LiveCollections supports, and for each one there will be both an explanation here and sample code in the app.
148+
149+
For most users, you probably don’t need to read past **Part 2 **of this post, it covers the 80% use case, when all data is in a single section.
150+
151+
However, if you’re using table views and collection views in more complex ways in your app, or if you’d like to see how you can fully leverage LiveCollections, then I encourage you to read to the end.
152+
153+
[Part 2: Single Section Views](https://medium.com/p/812436b004ea)
154+
155+
[Part 3: Multi-Section Views](https://medium.com/@stephane_57022/6525369decd2)
156+
157+
**If you want to work on changing how the world reads, come join us! [www.scribd.com/careers](https://www.scribd.com/careers)**
158+
159+
### Resources
160+
161+
[Download from Scribd’s GitHub repo](https://github.com/scribd/LiveCollections/)
162+
163+
![](https://cdn-images-1.medium.com/max/2000/1*hTHcvNhtABq_lMD0dVYQAA.png)

0 commit comments

Comments
 (0)