diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000000..a295f461e5 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1,2 @@ +asyncdisplaykit.org + diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000000..4bb498ec63 --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,420 @@ +--- +layout: page +title: License +permalink: /license/ +--- + +AsyncDisplayKit is free software under the BSD +license. + +Code examples and sample +projects are licensed as follows: + + This file provided by Facebook is for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +All other AsyncDisplayKit documentation is licensed CC-BY-4.0. + + Attribution 4.0 International + + ======================================================================= + + Creative Commons Corporation ("Creative Commons") is not a law firm and + does not provide legal services or legal advice. Distribution of + Creative Commons public licenses does not create a lawyer-client or + other relationship. Creative Commons makes its licenses and related + information available on an "as-is" basis. Creative Commons gives no + warranties regarding its licenses, any material licensed under their + terms and conditions, or any related information. Creative Commons + disclaims all liability for damages resulting from their use to the + fullest extent possible. + + Using Creative Commons Public Licenses + + Creative Commons public licenses provide a standard set of terms and + conditions that creators and other rights holders may use to share + original works of authorship and other material subject to copyright + and certain other rights specified in the public license below. The + following considerations are for informational purposes only, are not + exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + + ======================================================================= + + Creative Commons Attribution 4.0 International Public License + + By exercising the Licensed Rights (defined below), You accept and agree + to be bound by the terms and conditions of this Creative Commons + Attribution 4.0 International Public License ("Public License"). To the + extent this Public License may be interpreted as a contract, You are + granted the Licensed Rights in consideration of Your acceptance of + these terms and conditions, and the Licensor grants You such rights in + consideration of benefits the Licensor receives from making the + Licensed Material available under these terms and conditions. + + + Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + + Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + + Section 3 -- License Conditions. + + Your exercise of the Licensed Rights is expressly made subject to the + following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + + Section 4 -- Sui Generis Database Rights. + + Where the Licensed Rights include Sui Generis Database Rights that + apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + + For the avoidance of doubt, this Section 4 supplements and does not + replace Your obligations under this Public License where the Licensed + Rights include other Copyright and Similar Rights. + + + Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + + Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + + Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + + Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + + ======================================================================= + + Creative Commons is not a party to its public licenses. + Notwithstanding, Creative Commons may elect to apply one of its public + licenses to material it publishes and in those instances will be + considered the "Licensor." Except for the limited purpose of indicating + that material is shared under a Creative Commons public license or as + otherwise permitted by the Creative Commons policies published at + creativecommons.org/policies, Creative Commons does not authorize the + use of the trademark "Creative Commons" or any other trademark or logo + of Creative Commons without its prior written consent including, + without limitation, in connection with any unauthorized modifications + to any of its public licenses or any other arrangements, + understandings, or agreements concerning use of licensed material. For + the avoidance of doubt, this paragraph does not form part of the public + licenses. + + Creative Commons may be contacted at creativecommons.org. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..05980d3818 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +# Documentation + +## Building + +You need Jekyll and appledoc. See `build.sh`. + +## License + +See LICENSE.md. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..0d5f309f67 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,13 @@ +# Site settings +title: AsyncDisplayKit +description: Smooth asynchronous user interfaces for iOS apps. +baseurl: "" +url: "http://asyncdisplaykit.org" + +# Build settings +highlighter: pygments +markdown: redcarpet + +exclude: +- README.md +- build.sh diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html new file mode 100644 index 0000000000..2714963def --- /dev/null +++ b/docs/_includes/footer.html @@ -0,0 +1,18 @@ + diff --git a/docs/_includes/head.html b/docs/_includes/head.html new file mode 100644 index 0000000000..a9e7efbdea --- /dev/null +++ b/docs/_includes/head.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + {% if page.title %}{{ page.title }} — {% endif %}AsyncDisplayKit + + + + + + + diff --git a/docs/_includes/header.html b/docs/_includes/header.html new file mode 100644 index 0000000000..aebb67cdf1 --- /dev/null +++ b/docs/_includes/header.html @@ -0,0 +1,25 @@ + diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 0000000000..bdf5a388da --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,20 @@ + + + + {% include head.html %} + + + + {% include header.html %} + +
+
+ {{ content }} +
+
+ + {% include footer.html %} + + + + diff --git a/docs/_layouts/docs.html b/docs/_layouts/docs.html new file mode 100644 index 0000000000..65c07040aa --- /dev/null +++ b/docs/_layouts/docs.html @@ -0,0 +1,27 @@ +--- +layout: default +sectionid: docs +--- +
+ +
+

+ {{ page.title }} + [edit] +

+
+ +
+ {{ content }} +
+ +
+ {% if page.prev %} + ← prev + {% endif %} + {% if page.next %} + next → + {% endif %} +
+ +
diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html new file mode 100644 index 0000000000..8e7ccf7a15 --- /dev/null +++ b/docs/_layouts/page.html @@ -0,0 +1,16 @@ +--- +layout: default +--- +
+ + {% if page.shouldDisplayTitle %} +
+

{{ page.title }}

+
+ {% endif %} + +
+ {{ content }} +
+ +
diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html new file mode 100644 index 0000000000..675596fb1c --- /dev/null +++ b/docs/_layouts/post.html @@ -0,0 +1,15 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+ +
+ +
+ {{ content }} +
+ +
diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss new file mode 100644 index 0000000000..7d353be258 --- /dev/null +++ b/docs/_sass/_base.scss @@ -0,0 +1,205 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + + + +/** + * Basic styling + */ +body { + font-family: $base-font-family; + font-size: $base-font-size; + line-height: $base-line-height; + font-weight: 300; + color: $text-color; + background-color: $background-color; + -webkit-text-size-adjust: 100%; +} + + + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +%vertical-rhythm { + margin-bottom: $spacing-unit / 2; +} + + + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + + + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: $small-font-size; +} + + + +/** + * Lists + */ +ul, ol { + margin-left: $spacing-unit; +} + +li { + > ul, + > ol { + margin-bottom: 0; + } +} + + + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: 300; +} + + + +/** + * Links + */ +a { + color: $brand-color; + text-decoration: none; + + &:visited { + color: darken($brand-color, 15%); + } + + &:hover { + color: $text-color; + text-decoration: underline; + } +} + + + +/** + * Blockquotes + */ +blockquote { + color: $grey-color; + border-left: 4px solid $grey-color-light; + padding-left: $spacing-unit / 2; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; + + > :last-child { + margin-bottom: 0; + } +} + + + +/** + * Code formatting + */ +pre, +code { + font-family: Monaco, monospace; + font-size: 14px; + border: 1px solid #afe4ff; + border-radius: 3px; + background-color: #fafdff; +} + +code { + padding: 1px 5px; +} + +pre { + padding: 8px 12px; + overflow-x: scroll; + + > code { + border: 0; + padding-right: 0; + padding-left: 0; + } +} + + + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(800px - (#{$spacing-unit} * 2)); + max-width: calc(800px - (#{$spacing-unit} * 2)); + margin-right: auto; + margin-left: auto; + padding-right: $spacing-unit; + padding-left: $spacing-unit; + @extend %clearfix; + + @include media-query($on-laptop) { + max-width: -webkit-calc(800px - (#{$spacing-unit})); + max-width: calc(800px - (#{$spacing-unit})); + padding-right: $spacing-unit / 2; + padding-left: $spacing-unit / 2; + } +} + + + +/** + * Clearfix + */ +%clearfix { + + &:after { + content: ""; + display: table; + clear: both; + } +} + + + +/** + * Icons + */ +.icon { + + > svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + + path { + fill: $grey-color; + } + } +} diff --git a/docs/_sass/_layout.scss b/docs/_sass/_layout.scss new file mode 100644 index 0000000000..2eb740fa67 --- /dev/null +++ b/docs/_sass/_layout.scss @@ -0,0 +1,265 @@ +/** + * Site header + */ +.site-header { + border-top: 5px solid $grey-color-dark; + border-bottom: 1px solid $grey-color-light; + min-height: 56px; + background-color: #f8f8f8; + + // Positioning context for the mobile navigation icon + position: relative; +} + +.site-title { + font-size: 26px; + line-height: 56px; + letter-spacing: -1px; + margin-bottom: 0; + float: left; + + &, + &:visited { + color: $grey-color-dark; + } + &:hover { + text-decoration: none; + } +} + +.site-nav { + float: right; + line-height: 56px; + + .menu-icon { + display: none; + } + + .page-link { + color: $grey-color; + line-height: $base-line-height; + + // Gaps between nav items, but not on the first one + &:not(:first-child) { + margin-left: 20px; + } + + &:hover { + color: $text-color; + } + } + + .page-link-active { + color: $text-color; + } + + @include media-query($on-palm) { + position: absolute; + top: 9px; + right: 30px; + background-color: $background-color; + border: 1px solid $grey-color-light; + border-radius: 5px; + text-align: right; + + .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + + > svg { + width: 18px; + height: 15px; + + path { + fill: $grey-color-dark; + } + } + } + + .trigger { + clear: both; + display: none; + } + + &:hover .trigger { + display: block; + padding-bottom: 5px; + } + + .page-link { + display: block; + padding: 5px 10px; + } + } +} + + + +/** + * Site footer + */ +.site-footer { + border-top: 1px solid $grey-color-light; + padding: $spacing-unit 0; +} + +.footer-heading { + font-size: 18px; + margin-bottom: $spacing-unit / 2; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; +} + +.footer-col-wrapper { + font-size: 11px; + color: $grey-color; + margin-left: -$spacing-unit / 2; + @extend %clearfix; +} + +.footer-col { + float: left; + margin-bottom: $spacing-unit / 2; + padding-left: $spacing-unit / 2; +} + +.footer-col-left { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); +} + +.footer-col-right { + text-align: right; + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); +} + +@include media-query($on-laptop) { + .footer-col-left { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); + } + + .footer-col-right { + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + +@include media-query($on-palm) { + .footer-col { + float: none; + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + + + +/** + * Page content + */ +.page-content { + padding: $spacing-unit 0; + background-color: white; +} + +.page-heading { + font-size: 20px; +} + +.post-list { + margin-left: 0; + list-style: none; + + > li { + margin-bottom: $spacing-unit; + } +} + +.post-meta { + font-size: $small-font-size; + color: $grey-color; +} + +.post-link { + display: block; + font-size: 24px; +} + + + +/** + * Posts + */ +.post-header { + margin-bottom: $spacing-unit; +} + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; + + @include media-query($on-laptop) { + font-size: 36px; + } + + .edit-page-link { + font-size: 18px; + } +} + +.post-content { + margin-bottom: $spacing-unit; + + h2 { + font-size: 32px; + + @include media-query($on-laptop) { + font-size: 28px; + } + } + + h3 { + font-size: 26px; + + @include media-query($on-laptop) { + font-size: 22px; + } + } + + h4 { + font-size: 20px; + + @include media-query($on-laptop) { + font-size: 18px; + } + } +} + + + +/** + * Docs + */ +.docs-prevnext { + @extend %clearfix; +} + +.docs-prev { + float: left; +} + +.docs-next { + float: right; +} diff --git a/docs/_sass/_syntax-highlighting.scss b/docs/_sass/_syntax-highlighting.scss new file mode 100644 index 0000000000..3758fdb458 --- /dev/null +++ b/docs/_sass/_syntax-highlighting.scss @@ -0,0 +1,76 @@ +/** + * Syntax highlighting styles + */ + +/* not official Xcode colors, but looks better on the web */ +$xc-black: black; +$xc-green: #008d14; +$xc-red: #b72748; +$xc-blue: #103ffb; +$xc-turquoise: #3a95ba; + +.highlight { + background: #fff; + @extend %vertical-rhythm; + + .c { color: $xc-green; font-style: italic } // Comment + .err { color: #a61717; background-color: #e3d2d2 } // Error + .k { color: $xc-blue} // Keyword + .o { } // Operator + .cm { color: $xc-green; font-style: italic } // Comment.Multiline + .cp { color: $xc-red} // Comment.Preproc + .c1 { color: $xc-green; font-style: italic } // Comment.Single + .cs { color: $xc-green; font-weight: bold; font-style: italic } // Comment.Special + .gd { color: #000; background-color: #fdd } // Generic.Deleted + .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific + .ge { font-style: italic } // Generic.Emph + .gr { color: #a00 } // Generic.Error + .gh { color: #999 } // Generic.Heading + .gi { color: #000; background-color: #dfd } // Generic.Inserted + .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific + .go { color: #888 } // Generic.Output + .gp { color: #555 } // Generic.Prompt + .gs { font-weight: bold } // Generic.Strong + .gu { color: #aaa } // Generic.Subheading + .gt { color: #a00 } // Generic.Traceback + .kc { color: orange} // Keyword.Constant + .kd { color: orange} // Keyword.Declaration + .kp { color: $xc-green} // Keyword.Pseudo + .kr { color: $xc-green} // Keyword.Reserved + .kt { color: $xc-blue} // Keyword.Type + .m { color: orange } // Literal.Number + .s { color: $xc-red } // Literal.String + .na { color: orange } // Name.Attribute + .nb { color: $xc-blue } // Name.Builtin + .nc { color: $xc-turquoise } // Name.Class + .no { color: orange } // Name.Constant + .ni { color: orange } // Name.Entity + .ne { color: orange } // Name.Exception + .nf { } // Name.Function + .nn { color: orange } // Name.Namespace + .nt { color: orange } // Name.Tag + .nv { } // Name.Variable + .ow { } // Operator.Word + .w { color: #bbb } // Text.Whitespace + .mf {} // Literal.Number.Float + .mh { color: $xc-black } // Literal.Number.Hex + .mi { color: $xc-black } // Literal.Number.Integer + .mo { color: $xc-black } // Literal.Number.Oct + .il { color: $xc-black } // Literal.Number.Integer.Long + .sb { color: #d14 } // Literal.String.Backtick + .sc { color: #d14 } // Literal.String.Char + .sd { color: #d14 } // Literal.String.Doc + .s2 { color: #d14 } // Literal.String.Double + .se { color: #d14 } // Literal.String.Escape + .sh { color: #d14 } // Literal.String.Heredoc + .si { color: #d14 } // Literal.String.Interpol + .sx { color: #d14 } // Literal.String.Other + .sr { color: orange } // Literal.String.Regex + .s1 { color: $xc-red } // Literal.String.Single + .ss { color: $xc-red } // Literal.String.Symbol + .bp { color: $xc-turquoise } // Name.Builtin.Pseudo + .vc { color: $xc-turquoise } // Name.Variable.Class + .vg { color: $xc-black } // Name.Variable.Global + .vi { color: orange } // Name.Variable.Instance + .nl { color: $xc-turquoise } +} diff --git a/docs/assets/guide/1-shuffle-crop.png b/docs/assets/guide/1-shuffle-crop.png new file mode 100644 index 0000000000..d1e0a83b0c Binary files /dev/null and b/docs/assets/guide/1-shuffle-crop.png differ diff --git a/docs/assets/guide/1-shuffle.png b/docs/assets/guide/1-shuffle.png new file mode 100644 index 0000000000..9188eebf8a Binary files /dev/null and b/docs/assets/guide/1-shuffle.png differ diff --git a/docs/assets/logo-square.png b/docs/assets/logo-square.png new file mode 100755 index 0000000000..82ad66c69e Binary files /dev/null and b/docs/assets/logo-square.png differ diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100755 index 0000000000..dce1ebc950 Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/assets/node-view-layer.png b/docs/assets/node-view-layer.png new file mode 100755 index 0000000000..544294af8f Binary files /dev/null and b/docs/assets/node-view-layer.png differ diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 0000000000..c532bb0d6b --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -e + +HEADERS=`ls ../AsyncDisplayKit/*.h ../AsyncDisplayKit/Details/ASRangeController.h ../AsyncDisplayKit/Layout/*.h` + +rm -rf htdocs appledoc + +jekyll build --destination htdocs + +appledoc \ + --no-create-docset \ + --create-html \ + --exit-threshold 2 \ + --no-repeat-first-par \ + --no-merge-categories \ + --explicit-crossref \ + --warn-missing-output-path \ + --warn-missing-company-id \ + --warn-undocumented-object \ + --warn-undocumented-member \ + --warn-empty-description \ + --warn-unknown-directive \ + --warn-invalid-crossref \ + --warn-missing-arg \ + --project-name AsyncDisplayKit \ + --project-company Facebook \ + --company-id "com.facebook" \ + --output appledoc \ + $HEADERS + +mv appledoc/html htdocs/appledoc + +rmdir appledoc diff --git a/docs/css/main.scss b/docs/css/main.scss new file mode 100755 index 0000000000..4417ff0713 --- /dev/null +++ b/docs/css/main.scss @@ -0,0 +1,49 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- +@charset "utf-8"; + + + +// Our variables +$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +$base-font-size: 16px; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.5; + +$spacing-unit: 30px; + +$text-color: #111; +$background-color: #f8f8f8; +$brand-color: #21b6ff; + +$grey-color: #828282; +$grey-color-light: lighten($grey-color, 40%); +$grey-color-dark: darken($grey-color, 25%); + +$on-palm: 600px; +$on-laptop: 800px; + + + +// Using media queries with like this: +// @include media-query($palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + + + +// Import partials from `sass_dir` (defaults to `_sass`) +@import + "base", + "layout", + "syntax-highlighting" +; diff --git a/docs/guide/1-introduction.md b/docs/guide/1-introduction.md new file mode 100644 index 0000000000..d923c0a215 --- /dev/null +++ b/docs/guide/1-introduction.md @@ -0,0 +1,150 @@ +--- +layout: docs +title: Getting started +permalink: /guide/ +next: guide/2/ +--- + +## Concepts + +AsyncDisplayKit's basic unit is the *node*. ASDisplayNode is an abstraction +over UIView, which in turn is an abstraction over CALayer. Unlike views, which +can only be used on the main thread, nodes are thread-safe: you can +instantiate and configure entire hierarchies of them in parallel on background +threads. + +To keep its user interface smooth and responsive, your app should render at 60 +frames per second — the gold standard on iOS. This means the main thread +has one-sixtieth of a second to push each frame. That's 16 milliseconds to +execute all layout and drawing code! And because of system overhead, your code +usually has less than ten milliseconds to run before it causes a frame drop. + +AsyncDisplayKit lets you move image decoding, text sizing and rendering, and +other expensive UI operations off the main thread. It has other tricks up its +sleeve too... but we'll get to that later. :] + +## Nodes as drop-in view replacements + +If you're used to working with views, you already know how to use nodes. The +node API is similar to UIView's, with some additional conveniences — for +example, you can access common CALayer properties directly. To add a node to +an existing view or layer hierarchy, use its `node.view` or `node.layer`. + +AsyncDisplayKit's core components include: + +* *ASDisplayNode*. Counterpart to UIView — subclass to make custom nodes. +* *ASControlNode*. Analogous to UIControl — subclass to make buttons. +* *ASImageNode*. Like UIImageView — decodes images asynchronously. +* *ASTextNode*. Like UITextView — built on TextKit with full-featured + rich text support. +* *ASTableView* and *ASCollectionView*. UITableView and UICollectionView + subclasses that support nodes. + +You can use these as drop-in replacements for their UIKit counterparts. While +ASDK works most effectively with fully node-based hierarchies, even replacing +individual views with nodes can improve performance. + +Let's look at an example. + +We'll start out by using nodes synchronously on the main thread — the +same way you already use views. This code is a familiar sight in custom view +controller `-loadView` implementations: + +```objective-c +_imageView = [[UIImageView alloc] init]; +_imageView.image = [UIImage imageNamed:@"hello"]; +_imageView.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f); +[self.view addSubview:_imageView]; +``` + +We can replace it with the following node-based code: + +```objective-c +_imageNode = [[ASImageNode alloc] init]; +_imageNode.backgroundColor = [UIColor lightGrayColor]; +_imageNode.image = [UIImage imageNamed:@"hello"]; +_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f); +[self.view addSubview:_imageNode.view]; +``` + +This doesn't take advantage of ASDK's asynchronous sizing and layout +functionality, but it's already an improvement. The first block of code +synchronously decodes `hello.png` on the main thread; the second starts +decoding the image on a background thread, possibly on a different CPU core. + +(Note that we're setting a placeholder background colour on the node, "holding +its place" onscreen until the real content appears. This works well with +images but less so with text — people expect text to appear instantly, +with images loading in after a slight delay. We'll discuss techniques to +improve this later on.) + +## Button nodes + +ASImageNode and ASTextNode both inherit from ASControlNode, so you can use them +as buttons. Let's say we're making a music player and we want to add a +(non-skeuomorphic, iOS 7-style) shuffle button: + +[![shuffle]({{ site.baseurl }}/assets/guide/1-shuffle-crop.png)]({{ site.baseurl }}/assets/guide/1-shuffle.png) + +Our view controller will look something like this: + +```objective-c +- (void)viewDidLoad +{ + [super viewDidLoad]; + + // attribute a string + NSDictionary *attrs = @{ + NSFontAttributeName: [UIFont systemFontOfSize:12.0f], + NSForegroundColorAttributeName: [UIColor redColor], + }; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"shuffle" + attributes:attrs]; + + // create the node + _shuffleNode = [[ASTextNode alloc] init]; + _shuffleNode.attributedString = string; + + // configure the button + _shuffleNode.userInteractionEnabled = YES; // opt into touch handling + [_shuffleNode addTarget:self + action:@selector(buttonTapped:) + forControlEvents:ASControlNodeEventTouchUpInside]; + + // size all the things + CGRect b = self.view.bounds; // convenience + CGSize size = [_shuffleNode measure:CGSizeMake(b.size.width, FLT_MAX)]; + CGPoint origin = CGPointMake(roundf( (b.size.width - size.width) / 2.0f ), + roundf( (b.size.height - size.height) / 2.0f )); + _shuffleNode.frame = (CGRect){ origin, size }; + + // add to our view + [self.view addSubview:_shuffleNode.view]; +} + +- (void)buttonTapped:(id)sender +{ + NSLog(@"tapped!"); +} +``` + +This works as you would expect. Unfortunately, this button is only 14½ +points tall — nowhere near the standard 44×44 minimum tap target +size — and it's very difficult to tap. We could solve this by +subclassing the text node and overriding `-hitTest:withEvent:`. We could even +force the text view to have a minimum height during layout. But wouldn't it be +nice if there were a more elegant way? + +```objective-c + // size all the things + /* ... */ + + // make the tap target taller + CGFloat extendY = roundf( (44.0f - size.height) / 2.0f ); + _shuffleNode.hitTestSlop = UIEdgeInsetsMake(-extendY, 0.0f, -extendY, 0.0f); +``` + +Et voilà! *Hit-test slops* work on all nodes, and are a nice example of what +this new abstraction enables. + +Next up, making your own nodes! diff --git a/docs/guide/2-custom-nodes.md b/docs/guide/2-custom-nodes.md new file mode 100644 index 0000000000..6d67ed3a05 --- /dev/null +++ b/docs/guide/2-custom-nodes.md @@ -0,0 +1,211 @@ +--- +layout: docs +title: Custom nodes +permalink: /guide/2/ +prev: guide/ +next: guide/3/ +--- + +## View hierarchies + +Sizing and layout of custom view hierarchies are typically done all at once on +the main thread. For example, a custom UIView that minimally encloses a text +view and an image view might look like this: + +```objective-c +- (CGSize)sizeThatFits:(CGSize)size +{ + // size the image + CGSize imageSize = [_imageView sizeThatFits:size]; + + // size the text view + CGSize maxTextSize = CGSizeMake(size.width - imageSize.width, size.height); + CGSize textSize = [_textView sizeThatFits:maxTextSize]; + + // make sure everything fits + CGFloat minHeight = MAX(imageSize.height, textSize.height); + return CGSizeMake(size.width, minHeight); +} + +- (void)layoutSubviews +{ + CGSize size = self.bounds.size; // convenience + + // size and layout the image + CGSize imageSize = [_imageView sizeThatFits:size]; + _imageView.frame = CGRectMake(size.width - imageSize.width, 0.0f, + imageSize.width, imageSize.height); + + // size and layout the text view + CGSize maxTextSize = CGSizeMake(size.width - imageSize.width, size.height); + CGSize textSize = [_textView sizeThatFits:maxTextSize]; + _textView.frame = (CGRect){ CGPointZero, textSize }; +} +``` + +This isn't ideal. We're sizing our subviews twice — once to figure out +how big our view needs to be and once when laying it out — and while our +layout arithmetic is cheap and quick, we're also blocking the main thread on +expensive text sizing. + +We could improve the situation by manually cacheing our subviews' sizes, but +that solution comes with its own set of problems. Just adding `_imageSize` and +`_textSize` ivars wouldn't be enough: for example, if the text were to change, +we'd need to recompute its size. The boilerplate would quickly become +untenable. + +Further, even with a cache, we'll still be blocking the main thread on sizing +*sometimes*. We could try to shift sizing to a background thread with +`dispatch_async()`, but even if our own code is thread-safe, UIView methods are +documented to [only work on the main +thread](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html): + +> Manipulations to your application’s user interface must occur on the main +> thread. Thus, you should always call the methods of the UIView class from +> code running in the main thread of your application. The only time this may +> not be strictly necessary is when creating the view object itself but all +> other manipulations should occur on the main thread. + +This is a pretty deep rabbit hole. We could attempt to work around the fact +that UILabels and UITextViews cannot safely be sized on background threads by +manually creating a TextKit stack and sizing the text ourselves... but that's a +laborious duplication of work. Further, if UITextView's layout behaviour +changes in an iOS update, our sizing code will break. (And did we mention that +TextKit isn't thread-safe either?) + +## Node hierarchies + +Enter AsyncDisplayKit. Our custom node looks like this: + +```objective-c +#import + +... + +// perform expensive sizing operations on a background thread +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + // size the image + CGSize imageSize = [_imageNode measure:constrainedSize]; + + // size the text node + CGSize maxTextSize = CGSizeMake(constrainedSize.width - imageSize.width, + constrainedSize.height); + CGSize textSize = [_textNode measure:maxTextSize]; + + // make sure everything fits + CGFloat minHeight = MAX(imageSize.height, textSize.height); + return CGSizeMake(constrainedSize.width, minHeight); +} + +// do as little work as possible in main-thread layout +- (void)layout +{ + // layout the image using its cached size + CGSize imageSize = _imageNode.calculatedSize; + _imageNode.frame = CGRectMake(self.bounds.size.width - imageSize.width, 0.0f, + imageSize.width, imageSize.height); + + // layout the text view using its cached size + CGSize textSize = _textNode.calculatedSize; + _textNode.frame = (CGRect){ CGPointZero, textSize }; +} +``` + +ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe, +so we can size them on background threads. The `-measure:` method is like +`-sizeThatFits:`, but with side effects: it caches both the argument +(`constrainedSizeForCalculatedSize`) and the result (`calculatedSize`) for +quick access later on — like in our now-snappy `-layout` implementation. + +As you can see, node hierarchies are sized and laid out in much the same way as +their view counterparts. Custom nodes do need to be written with a few things +in mind: + +* Nodes must recursively measure all of their subnodes in their + `-calculateSizeThatFits:` implementations. Note that the `-measure:` + machinery will only call `-calculateSizeThatFits:` if a new measurement pass + is needed (e.g., if the constrained size has changed). + +* Nodes should perform any other expensive pre-layout calculations in + `-calculateSizeThatFits:`, cacheing useful intermediate results in ivars as + appropriate. + +* Nodes should call `[self invalidateCalculatedSize]` when necessary. For + example, ASTextNode invalidates its calculated size when its + `attributedString` property is changed. + +For more examples of custom sizing and layout, along with a demo of +ASTextNode's features, check out `BlurbNode` and `KittenNode` in the +[Kittens](https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens) +sample project. + +## Custom drawing + +To guarantee thread safety in its highly-concurrent drawing system, the node +drawing API diverges substantially from UIView's. Instead of implementing +`-drawRect:`, you must: + +1. Define an internal "draw parameters" class for your custom node. This + class should be able to store any state your node needs to draw itself + — it can be a plain old NSObject or even a dictionary. + +2. Return a configured instance of your draw parameters class in + `-drawParametersForAsyncLayer:`. This method will always be called on the + main thread. + +3. Implement either `+drawRect:withParameters:isCancelled:isRasterizing:` or + `+displayWithParameters:isCancelled:`. Note that these are *class* methods + that will not have access to your node's state — only the draw + parameters object. They can be called on any thread and must be + thread-safe. + +For example, this node will draw a rainbow: + +```objective-c +@interface RainbowNode : ASDisplayNode +@end + +@implementation RainbowNode + ++ (void)drawRect:(CGRect)bounds + withParameters:(id)parameters + isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock + isRasterizing:(BOOL)isRasterizing +{ + // clear the backing store, but only if we're not rasterising into another layer + if (!isRasterizing) { + [[UIColor whiteColor] set]; + UIRectFill(bounds); + } + + // UIColor sadly lacks +indigoColor and +violetColor methods + NSArray *colors = @[ [UIColor redColor], + [UIColor orangeColor], + [UIColor yellowColor], + [UIColor greenColor], + [UIColor blueColor], + [UIColor purpleColor] ]; + CGFloat stripeHeight = roundf(bounds.size.height / (float)colors.count); + + // draw the stripes + for (UIColor *color in colors) { + CGRect stripe = CGRectZero; + CGRectDivide(bounds, &stripe, &bounds, stripeHeight, CGRectMinYEdge); + [color set]; + UIRectFill(stripe); + } +} + +@end +``` + +This could easily be extended to support vertical rainbows too, by adding a +`vertical` property to the node, exporting it in +`-drawParametersForAsyncLayer:`, and referencing it in +`+drawRect:withParameters:isCancelled:isRasterizing:`. More-complex nodes can +be supported in much the same way. + +For more on custom nodes, check out the [subclassing +header](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h) +or read on! diff --git a/docs/guide/3-asynchronous-display.md b/docs/guide/3-asynchronous-display.md new file mode 100644 index 0000000000..acc9f37911 --- /dev/null +++ b/docs/guide/3-asynchronous-display.md @@ -0,0 +1,102 @@ +--- +layout: docs +title: Asynchronous display +permalink: /guide/3/ +prev: guide/2/ +next: guide/4/ +--- + +## Realistic placeholders + +Nodes need to complete both a *measurement pass* and a *display pass* before +they're fully rendered. It's possible to force either step to happen +synchronously: call `-measure:` in `-layoutSubviews` to perform sizing on the +main thread, or set a node's `displaysAsynchronously` flag to NO to disable +ASDK's async display machinery. (AsyncDisplayKit can still improve your app's +performance even when rendering fully synchronously — more on that +later!) + +The recommended way to use ASDK is to only add nodes to your view hierarchy +once they've been sized. This avoids unsightly layout changes as the +measurement pass completes, but if you enable asynchronous display, it will +always be possible for a node to appear onscreen before its content has fully +rendered. We'll discuss techniques to minimise this shortly, but you should +take it into account and include *realistic placeholders* in your app designs. + +Once its measurement pass has completed, a node can accurately place all of its +subnodes onscreen — they'll just be blank. The easiest way to make a +realistic placeholder is to set static background colours on your subnodes. +This effect looks better than generic placeholder images because it varies +based on the content being loaded, and it works particularly well for opaque +images. You can also create visually-appealing placeholder nodes, like the +shimmery lines representing text in Paper as its stories are loaded, and swap +them out with your content nodes once they've finished displaying. + +## Working range + +So far, we've only discussed asynchronous sizing: toss a "create a node +hierarchy and measure it" block onto a background thread, then trampoline to +the main thread to add it to the view hierarchy when that's done. Ideally, as +much content as possible should be fully-rendered and ready to go as soon as +the user scrolls to it. This requires triggering display passes in advance. + +If your app's content is in a scroll view or can be paged through, like +Instagram's main feed or Paper's story strip, the solution is a *working +range*. A working range controller tracks the *visible range*, the subset of +content that's currently visible onscreen, and enqueues asynchronous rendering +for the next few screenfuls of content. As the user scrolls, a screenful or +two of previous content is preserved; the rest is cleared to conserve memory. +If she starts scrolling in the other direction, the working range trashes its +render queue and starts pre-rendering in the new direction of scroll — +and because of the buffer of previous content, this entire process will +typically be invisible. + +AsyncDisplayKit includes a generic working range controller, +`ASRangeController`. Its working range size can be tuned depending on your +app: if your nodes are simple, even an iPhone 4 can maintain a substantial +working range, but heavyweight nodes like Facebook stories are expensive and +need to be pruned quickly. + +```objective-c +ASRangeController *rangeController = [[ASRangeController alloc] init]; +rangeController.tuningParameters = (ASRangeTuningParameters){ + .leadingBufferScreenfuls = 2.0f; // two screenfuls in the direction of scroll + .trailingBufferScreenfuls = 0.5f; // one-half screenful in the other direction +}; +``` + +If you use a working range, you should profile your app and consider tuning it +differently on a per-device basis. iPhone 4 has 512MB of RAM and a single-core +A4 chipset, while iPhone 6 has 1GB of RAM and the orders-of-magnitude-faster +multicore A8 — and if your app supports iOS 7, it will be used on both. + +## ASTableView + +ASRangeController manages working ranges, but doesn't actually display content. +If your content is currently rendered in a UITableView, you can convert it to +use `ASTableView` and custom nodes — just subclass `ASCellNode` instead +of ASDisplayNode. ASTableView is a UITableView subclass that integrates +node-based cells and a working range. + +ASTableView doesn't let cells onscreen until their underlying nodes have been +sized, and as such can fully benefit from realistic placeholders. Its API is +very similar to UITableView (see the +[Kittens](https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens) +sample project for an example), with some key changes: + +* Rather than setting the table view's `.delegate` and `.dataSource`, you set + its `.asyncDelegate` and `.asyncDataSource`. See + [ASTableView.h](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASTableView.h) + for how its delegate and data source protocols differ from UITableView's. + +* Instead of implementing `-tableView:cellForRowAtIndexPath:`, your data + source must implement `-tableView:nodeForRowAtIndexPath:`. This method must + be thread-safe and should not implement reuse. Unlike the UITableView + version, it won't be called when the row is about to display. + +* `-tableView:heightForRowAtIndexPath:` has been removed — ASTableView + lets your cell nodes size themselves. This means you no longer have to + manually duplicate or factor out layout and sizing logic for + dynamically-sized UITableViewCells! + +Next up, how to get the most out of ASDK in your app. diff --git a/docs/guide/4-making-the-most-of-asdk.md b/docs/guide/4-making-the-most-of-asdk.md new file mode 100644 index 0000000000..f35ad900b4 --- /dev/null +++ b/docs/guide/4-making-the-most-of-asdk.md @@ -0,0 +1,139 @@ +--- +layout: docs +title: Making the most of AsyncDisplayKit +permalink: /guide/4/ +prev: guide/3/ +next: guide/5/ +--- + +## A note on optimisation + +AsyncDisplayKit is powerful and flexible, but it is not a panacea. If your app +has a complex image- or text-heavy user interface, ASDK can definitely help +improve its performance — but if you're blocking the main thread on +network requests, you should consider rearchitecting a few things first. :] + +So why is it worthwhile to change the way we do view layout and rendering, +given that UIKit has always been locked to the main thread and performant iOS +apps have been shipping since iPhone's launch? + +### Modern animations + +Until iOS 7, static animations (à la `+[UIView +animateWithDuration:animations:]`) were the standard. The post-skeuomorphism +redesign brought with it highly-interactive, physics-based animations, with +springs joining the ranks of constant animation functions like +`UIViewAnimationOptionCurveEaseInOut`. + +Classic animations aren't actually executed in your app. They're executed +out-of-process, in the high-priority Core Animation render server. Thanks to +pre-emptive multitasking, an app can block its main thread continuously without +causing the animation to drop a single frame. + +Critically, dynamic animations can't be offloaded the same way, and both +[pop](https://github.com/facebook/pop) and UIKit Dynamics execute physics +simulations on your app's main thread. This is because executing arbitrary +code in the render server would introduce unacceptable latency, even if it +could be done securely. + +Physics-based animations are often interactive, letting you start an animation +and interrupt it before it completes. Paper lets you fling objects across the +screen and catch them before they land, or grab a view that's being pulled by a +spring and tear it off. This requires processing touch events and updating +animation targets in realtime — even short delays for inter-process +communication would shatter the illusion. + +(Fun fact: Inertial scrolling is also a physics animation! UIScrollView has +always implemented its animations on the main thread, which is why stuttery +scrolling is the hallmark of a slow app.) + +### The main-thread bottleneck + +Physics animations aren't the only thing that need to happen on the main +thread. The main thread's [run +loop](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/multithreading/runloopmanagement/runloopmanagement.html) +is responsible for handling touch events and initiating drawing operations +— and with UIKit in the mix, it also has to render text, decode images, +and do any other custom drawing (e.g., using Core Graphics). + +If an iteration of the main thread's run loop takes too long, it will drop an +animation frame and may fail to handle touch events in time. Each iteration of +the run loop must complete within 16ms in order to drive 60fps animations, and +your own code typically has less than 10ms to execute. This means that the +best way to keep your app smooth and responsive is to do as little work on the +main thread as possible. + +What's more, the main thread only executes on one core! Single-threaded view +hierarchies can't take advantage of the multicore CPUs in all modern iOS +devices. This is important for more than just performance reasons — it's +also critical for battery life. Running the CPU on all cores for a short time +is preferable to running one core for an extended amount of time: if the +processor can *race to sleep* by finishing its work and idling faster, it can +spend more time in a low-power mode, improving battery life. + +## When to go asynchronous + +AsyncDisplayKit really shines when used fully asynchronously, shifting both +measurement and rendering passes off the main thread and onto multiple cores. +This requires a completely node-based hierarchy. Just as degrading from +UIViews to CALayers disables view-specific functionality like touch handling +from that point on, degrading from nodes to views disables async behaviour. + +You don't, however, need to convert your app's entire view hierarchy to nodes. +In fact, you shouldn't! Asynchronously bringing up your app's core UI, like +navigation elements or tab bars, would be a very confusing experience. Those +elements of your apps can still be nodes, but should be fully synchronous to +guarantee a fully-usable interface as quickly as possible. (This is why +UIWindow has no node equivalent.) + +There are two key situations where asynchronous hierarchies excel: + +1. *Parallelisation*. Measuring and rendering UITableViewCells (or your app's + equivalent, e.g., story cards in Paper) are embarrassingly parallel + problems. Table cells typically have a fixed width and variable height + determined by their contents — the argument to `-measure:` for one + cell doesn't depend on any other cells' calculations, so we can enqueue an + arbitrary number of cell measurement passes at once. + +2. *Preloading*. An app with five tabs should synchronously load the first + one so content is ready to go as quickly as possible. Once this is + complete and the CPU is idle, why not asynchronously prepare the other tabs + so the user doesn't have to wait? This is inconvenient with views, but + very easy with nodes. + +Paper's asynchronous rendering starts at the story strip. You should profile +your app and watch how people use it in the wild to decide what combination of +synchrony and asynchrony yields the best user experience. + +## Additional optimisations + +Complex hierarchies — even when rendered asynchronously — can +impose a cost because of the sheer number of views involved. Working around +this can be painful, but AsyncDisplayKit makes it easy! + +* *Layer-backing*. In some cases, you can substantially improve your app's + performance by using layers instead of views. Manually converting + view-based code to layers is laborious due to the difference in APIs. + Worse, if at some point you need to enable touch handling or other + view-specific functionality, you have to manually convert everything back + (and risk regressions!). + + With nodes, converting an entire subtree from views to layers is as simple + as... + + rootNode.layerBacked = YES; + + ...and if you need to go back, it's as simple as deleting one line. We + recommend enabling layer-backing as a matter of course in any custom node + that doesn't need touch handling. + +* *Precompositing*. Flattening an entire view hierarchy into a single layer + also improves performance, but comes with a hit to maintainability and + hierarchy-based reasoning. Nodes can do this for you too! + + rootNode.shouldRasterizeDescendants = YES; + + ...will cause the entire node hierarchy from that point on to be rendered + into one layer. + +Next up: AsyncDisplayKit, under the hood. diff --git a/docs/guide/5-under-the-hood.md b/docs/guide/5-under-the-hood.md new file mode 100644 index 0000000000..01229ec3ff --- /dev/null +++ b/docs/guide/5-under-the-hood.md @@ -0,0 +1,89 @@ +--- +layout: docs +title: Under the hood +permalink: /guide/5/ +prev: guide/4/ +--- + +## Node architecture + +*(Skip to the next section if you're not interested in AsyncDisplayKit implementation details.)* + +We've described nodes as an abstraction over views and layers, and shown how to +interact with the underlying UIViews and CALayers when necessary. Nodes don't +wrap or vend their UIKit counterparts, though — an ASImageNode's `.view` +is not a UIImageView! So how do nodes work? + +**NOTE:** Classes whose names begin with `_` are private. Don't use them +directly! + +Creating a node doesn't create its underlying view-layer pair. This is why you +can create nodes cheaply and on background threads. When you use a UIView or +CALayer property on a node, you're actually interacting with a proxy object, +[`_ASPendingState`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/_ASPendingState.h), +that's preconfigured to match UIView and CALayer defaults. + +The first access to a node's `.view` or `.layer` property causes both to be +initialised and configured with the node's current state. If it has subnodes, +they are recursively loaded as well. Once a node has been loaded, the proxy +object is destroyed and the node becomes main-thread-affined — its +properties will update the underlying view directly. (Layer-backed nodes do +the same, not loading views.) + +Nodes are powered by +[`_ASDisplayLayer`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayLayer.h) +and +[`_ASDisplayView`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayView.h). +These are lightweight to create and add to their respective hierarchies, and +provide integration points that allow nodes to act as full-fledged views or +layers. It's possible to create nodes that are backed by custom view or layer +classes, but doing so is strongly discouraged as it disables the majority of +ASDK's functionality. + +When Core Animation asks an `_ASDisplayLayer` to draw itself, the request is +forwarded to its node. Unless asynchronous display has been disabled, the +actual draw call won't happen immediately or on the main thread. Instead, a +display block will be added to a background queue. These blocks are executed +in parallel, but you can enable `ASDISPLAYNODE_DELAY_DISPLAY` in +[`ASDisplayNode(AsyncDisplay)`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/ASDisplayNode%2BAsyncDisplay.mm) +to serialise the render system for debugging. + +Common UIView subclass hooks are forwarded from `_ASDisplayView` to its +underlying node, including touch handling, hit-testing, and gesture recogniser +delegate calls. Because an `_ASDisplayView`'s layer is an `_ASDisplayLayer`, +view-backed nodes also participate in asynchronous display. + +## In practice + +What does this mean for your custom nodes? + +You can implement methods like `-touchesBegan:withEvent:` / +`touchesMoved:withEvent:` / `touchesEnded:withEvent:` / +`touchesCancelled:withEvent:` in your nodes exactly as you would in a UIView +subclass. If you find you need a subclass hook that hasn't already been +provided, please file an issue on GitHub — or add it yourself and submit a +pull request! + +If you need to interact or configure your node's underlying view or layer, +don't do so in `-init`. Instead, override `-didLoad` and check if you're +layer-backed: + +```objective-c +- (void)didLoad +{ + [super didLoad]; + + // add a gesture recogniser, if we have a view to add it to + if (!self.layerBacked) { + _gestureRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(_tap:)]; + [self.view addGestureRecognizer:_gestureRecogniser]; + } +} +``` + +## *fin.* + +Thanks for reading! If you have any questions, please file a GitHub issue or +post in the [Facebook group](https://www.facebook.com/groups/551597518288687). +We'd love to hear from you. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..ff8065921a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,86 @@ +--- +layout: page +title: Smooth asynchronous user interfaces for iOS apps +--- + +![logo]({{ site.baseurl }}/assets/logo.png) + +AsyncDisplayKit is an iOS framework that keeps even the most complex user +interfaces smooth and responsive. It was originally built to make Facebook's +[Paper](https://facebook.com/paper) possible, and goes hand-in-hand with +[pop](https://github.com/facebook/pop)'s physics-based animations — but +it's just as powerful with UIKit Dynamics and conventional app designs. + + +
+### Quick start + +ASDK is available on [CocoaPods](http://cocoapods.org). Add the following to your Podfile: + +```ruby +pod 'AsyncDisplayKit' +``` + +(ASDK can also be used as a regular static library: Copy the project to your +codebase manually, adding `AsyncDisplayKit.xcodeproj` to your workspace. Add +`libAsyncDisplayKit.a`, AssetsLibrary, and Photos to the "Link Binary With +Libraries" build phase. Include `-lc++ -ObjC` in your project linker flags.) + +Import the framework header, or create an [Objective-C bridging +header](https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/MixandMatch.html) +if you're using Swift: + +```objective-c +#import +``` + +AsyncDisplayKit Nodes are a thread-safe abstraction layer over UIViews and +CALayers: + +![logo]({{ site.baseurl }}/assets/node-view-layer.png) + +You can construct entire node hierarchies in parallel, or instantiate and size +a single node on a background thread — for example, you could do +something like this in a UIViewController: + +```objective-c +dispatch_async(_backgroundQueue, ^{ + ASTextNode *node = [[ASTextNode alloc] init]; + node.attributedString = [[NSAttributedString alloc] initWithString:@"hello!" + attributes:nil]; + [node measure:CGSizeMake(screenWidth, FLT_MAX)]; + node.frame = (CGRect){ CGPointZero, node.calculatedSize }; + + // self.view isn't a node, so we can only use it on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view addSubview:node.view]; + }); +}); +``` + +AsyncDisplayKit at a glance: + +* `ASImageNode` and `ASTextNode` are drop-in replacements for UIImageView and + UITextView. +* `ASMultiplexImageNode` can load and display progressively higher-quality + variants of an image over a slow cell network, letting you quickly show a + low-resolution photo while the full size downloads. +* `ASNetworkImageNode` is a simpler, single-image counterpart to the Multiplex + node. +* `ASTableView` and `ASCollectionView` are a node-aware UITableView and + UICollectionView, respectively, that can asynchronously preload cell nodes + — from loading network data to rendering — all without blocking + the main thread. + +You can also easily [create your own +nodes](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h) +to implement node hierarchies or custom drawing. + + +
+### Learn more + +* Read the [Getting Started guide]({{ site.baseurl }}/guide) +* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples) +* Browse the [API reference]({{ site.baseurl }}/appledoc) +* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q)